Triggers an action. Apply to <button>. Apply to <a> only when the control navigates. Parts have a 2px keyline and a brick edge that drops when pressed.
Pick this part
Pick c-button for
- The control does something on this page: submits a form, saves, deletes, opens a dialog, toggles a setting.
- A confirming or cancelling action in a form or
c-modalfooter. - An on/off toolbar control such as Bold or Mute, with aria-pressed.
Pick another part for
- The control goes to another page or URL: use a plain link. Apply
c-buttonto an<a>only when a link must look like a button, such as a start-page call to action. - Showing a status that does nothing when clicked: use
c-badge. - Filtering or removing items in a list of tags: that is a chip, which MCSS-Lite does not ship yet.
Build it
- Start with
<button>and addclass="c-button". - Pick at most one variant modifier:
--primary,--secondary,--ghost,--danger. - Pick at most one size modifier:
--sm,--lg. - Add
data-state="disabled"when the action cannot run right now. Preferaria-disabled="true": the button stays focusable, so link a visible reason witharia-describedbyand ignore activation in your handler. Thedisabledattribute also works on<button>but removes it from the tab order. On<a>, setaria-disabled="true"and removehref. - Add
data-state="loading"when the action is in progress. Setaria-busy="true"andaria-disabled="true", keep the visible label (for example "Saving…"), and ignore activation in your handler.
Add a publish date to schedule the post.
Show markup 26 lines
<div class="l-cluster">
<button type="button" class="c-button c-button--ghost">Cancel</button>
<button type="button" class="c-button">Save draft</button>
<button type="button" class="c-button c-button--primary">Publish post</button>
</div>
<div class="l-cluster">
<button type="button" class="c-button c-button--secondary c-button--sm">Export CSV</button>
<button type="button" class="c-button c-button--secondary">Duplicate post</button>
<button type="button" class="c-button c-button--lg">Create a project</button>
</div>
<div class="l-stack l-stack--sm">
<div class="l-cluster">
<button type="button" class="c-button" data-state="disabled" aria-disabled="true" aria-describedby="schedule-reason">Schedule post</button>
<button type="button" class="c-button" data-state="loading" aria-busy="true" aria-disabled="true">Saving…</button>
</div>
<p class="u-margin-0" id="schedule-reason">Add a publish date to schedule the post.</p>
</div>
<div class="l-cluster" role="group" aria-label="View">
<button type="button" class="c-button c-button--sm" aria-pressed="true">List</button>
<button type="button" class="c-button c-button--sm" aria-pressed="false">Grid</button>
</div>
<button type="button" class="c-button c-button--danger">Delete project</button>
Check your build
Each piece that doesn't fit was run through mcss-lite validate when this page was built. The output below is real.
Fits
Use one primary button per region, placed last in its group.
Show markup 4 lines
<div class="l-cluster">
<button type="button" class="c-button c-button--ghost">Cancel</button>
<button type="submit" class="c-button c-button--primary">Save changes</button>
</div>
validate: 0 issues
Doesn't fit
Don't combine two variants on one button to make it stand out more.
<button type="submit" class="c-button c-button--primary c-button--danger">Save changes</button>
validate
- error "c-button--primary" and "c-button--danger" are both variant modifiers; use only one.
[exclusive-modifiers]
Fits
Keep an unavailable action focusable and say why it can't run, with aria-disabled and a linked reason.
Add a title before you publish.
Show markup 4 lines
<div class="l-stack l-stack--sm">
<button type="button" class="c-button c-button--primary" data-state="disabled" aria-disabled="true" aria-describedby="publish-why">Publish</button>
<p id="publish-why" class="u-margin-0">Add a title before you publish.</p>
</div>
validate: 0 issues
Doesn't fit
Don't grey out a button without saying why, or set the state without its native pair.
<button type="button" class="c-button" data-state="disabled">Publish</button>
validate
- warning data-state="disabled" on <button> also needs aria-disabled="true" (keeps it focusable; link the reason with aria-describedby) or the disabled attribute.
[state-pair]
Fits
Invent nothing: a warning-style action is c-button--danger or a plain button.
<button type="button" class="c-button c-button--danger">Delete project</button>
validate: 0 issues
Doesn't fit
Don't guess a modifier that isn't in the contract.
<button type="button" class="c-button c-button--warning">Delete project</button>
validate
- error Unknown class "c-button--warning". Valid modifiers of c-button: --primary, --secondary, --ghost, --danger, --sm, --lg. Only classes from the MCSS-Lite contracts exist.
[unknown-class]
Fits
Label the button with what happens: a verb and the thing it acts on.
<button type="submit" class="c-button c-button--primary">Create account</button>
validate: 0 issues
Doesn't fit
Don't use vague labels: "OK" or "Submit" say nothing when a screen reader lists every button on the page.
<button type="submit" class="c-button c-button--primary">Submit</button>
validate
- warning "Submit" says nothing out of context. Name the action and its object, e.g. "Save changes" or "Download the invoice".
[content-vague]
Fits
Use a real <button> for actions; it brings focus, Enter and Space for free.
<button type="button" class="c-button" hx-post="/projects/42/archive">Archive project</button>
validate: 0 issues
Doesn't fit
Don't make a div clickable: it has no role, no focus and no keyboard, even with the button classes.
<div class="c-button" hx-post="/projects/42/archive">Archive project</div>
validate
- warning A <div> that reacts to clicks has no role, no keyboard access and no focus. Use <button type="button"> for actions or <a href> for navigation.
[clickable-div]
Spec sheet
Apply to <button> <a>
| Class | Group | What it does |
|---|---|---|
c-button--primary | variant (pick one) | The main action in a view region. Use at most one per region. |
c-button--secondary | variant (pick one) | Outlined in the action color; for important but not main actions. |
c-button--ghost | variant (pick one) | No border or background; for low-emphasis actions such as Cancel. |
c-button--danger | variant (pick one) | Destructive, hard-to-undo actions such as Delete. Brick red. |
c-button--sm | size (pick one) | Compact button for dense UI such as tables and toolbars. |
c-button--lg | size (pick one) | Large button for hero or empty-state calls to action. |
| State | Meaning | Pair it with |
|---|---|---|
data-state="disabled" | The action cannot run right now. Rendered ghosted with a dashed keyline. | Prefer aria-disabled="true": the button stays focusable, so link a visible reason with aria-describedby and ignore activation in your handler. The disabled attribute also works on <button> but removes it from the tab order. On <a>, set aria-disabled="true" and remove href. |
data-state="loading" | The action is in progress. Shows moving stripes; text keeps full contrast. | Set aria-busy="true" and aria-disabled="true", keep the visible label (for example "Saving…"), and ignore activation in your handler. |
Show tokens 21
| Token | Value (light / dark) |
|---|---|
--button-background | #ffffff / #14294d |
--button-background-hover | #eef6fc / #1b335c |
--button-border | #1a1d20 / #a9bacb |
--button-text | #1a1d20 / #e6edf4 |
--button-radius | 0.25rem |
--button-shadow | 0 2px 0 #1a1d20, 0 3px 8px rgb(26 29 32 / 0.12) / 0 2px 0 rgb(207 218 229 / 0.45), 0 4px 10px rgb(0 0 0 / 0.35) |
--button-primary-background | #005a9c / #2576cc |
--button-primary-background-hover | #004a80 / #185da6 |
--button-primary-text | #ffffff |
--button-secondary-text | #005a9c / #7cbff1 |
--button-secondary-border | #005a9c / #2576cc |
--button-ghost-background-hover | #e9ecef / #27436f |
--button-danger-background | #c91a09 |
--button-danger-background-hover | #a01408 |
--button-danger-text | #ffffff |
--button-pressed-background | #1a1d20 / #e6edf4 |
--button-pressed-border | #1a1d20 / #cfdae5 |
--button-pressed-text | #ffffff / #0f2140 |
--button-primary-border | #1a1d20 / #cfdae5 |
--button-danger-border | #1a1d20 / #cfdae5 |
--button-primary-background-active | #003860 / #144d8a |
Accessibility
- A button needs a visible text label, or aria-label when it only contains an icon.
- Do not remove the :focus-visible outline.
- Use type="button" unless the button submits a form.
c-button--smlooks compact but keeps a 44px tall hit area.- Don't disable a submit button until the form is valid. Let people submit, then show what is missing.
| Key | What it does |
|---|---|
| Enter | Activates the button. |
| Space | Activates the button (not links). |
| Tab | Moves focus to the button, including one marked aria-disabled. |
Wording
- Write a verb plus the thing it acts on: "Save changes", "Delete project". Never "OK", "Submit" or "Click here".
- Use sentence case: "Create account", not "Create Account".
- Keep the label the same when a toggle changes state; aria-pressed carries the state.
- A danger button names what is destroyed: "Delete account", not "Confirm".
- No full stop at the end of a label.