Button
The native push button in the four action tones; primary is the only filled button at rest, destructive fills on hover and always needs a confirmation or an undo.
stable · light DOM · 4.0.0. Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
<j3w1-button id="example-button-default"><button class="button" type="button">
<svg class="button-icon" aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square"><path d="M3 8.5l3 3 7-7"></path></svg>
<span class="button-loading" aria-hidden="true">⋯</span>
<span class="button-label">Save changes</span>
</button></j3w1-button>secondary
<j3w1-button id="example-button-secondary"><button class="button button-secondary" type="button">
<span class="button-loading" aria-hidden="true">⋯</span>
<span class="button-label">Preview</span>
</button></j3w1-button>tertiary
<j3w1-button id="example-button-tertiary"><button class="button button-tertiary" type="button">
<span class="button-loading" aria-hidden="true">⋯</span>
<span class="button-label">Cancel</span>
</button></j3w1-button>destructive
<j3w1-button id="example-button-destructive"><button class="button button-destructive" type="button">
<span class="button-loading" aria-hidden="true">⋯</span>
<span class="button-label">Delete workspace</span>
</button></j3w1-button>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/button.css';
import '@j3w1/ui/register/button';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy button --out ./vendor/j3w1/buttonThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
tone | CustomEvent | Button tone: primary, secondary, tertiary or destructive. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-action | CustomEvent | A native button activated. Prevent default on this event to cancel the associated native default action. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Button button
The native push button in the four action tones; primary is the only filled button at rest, destructive fills on hover and always needs a confirmation or an undo.
Report an issue with this component
View source and copy recipe · Download standalone example
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
root.bg | color.action.primary.bgapproveduse | #7d1310 | Primary button fill; D-026 darkened it off color12, which stays fixed as heritage ANSI 12. |
root.text | color.action.primary.textapproveduse | #ffa2a7 | Primary action labels: bright rose across rest, hover and pressed states (D-033). |
root.border | color.action.primary.borderobserveduse | #9e231f | Primary button border. |
root.bg-hover | color.action.primary.hover-bgobserveduse | #911410 | Primary button hover; pair with action.primary.text (D-033). |
root.bg-active | color.action.primary.pressed-bgobserveduse | #630f0d | Primary button pressed. |
root.ring | color.interaction.focus.ring-containerobserveduse | #ffa2a7 | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry. |
secondary.bg | color.action.secondary.bgobserveduse | rgb(0 0 0 / 0%) | Secondary button has no fill. |
secondary.text | color.action.secondary.textapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
secondary.border | color.action.secondary.borderapproveduse | #986f72 | D-041 action.secondary.border: color.primitive.modern.control. |
secondary.bg-hover | color.action.secondary.hover-bgobserveduse | #630f0d | Secondary button hover. |
secondary.bg-active | color.action.secondary.pressed-bgobserveduse | #420f0c | Secondary button pressed. |
secondary.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
tertiary.text | color.action.tertiary.textapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
tertiary.bg-hover | color.action.tertiary.hover-bgobserveduse | #1c0a09 | Ghost and icon button hover. |
tertiary.bg-active | color.interaction.pressed.bgobserveduse | #420f0c | Pressed fill. |
destructive.text | color.action.destructive.textobserveduse | #f07178 | D-041 destructive foreground remains readable on native warning tints. |
destructive.border | color.action.destructive.borderobserveduse | #e53935 | Destructive button border. |
destructive.bg-hover | color.action.destructive.hover-bgobserveduse | #dc282e | Destructive button hover fill. |
destructive.text-hover | color.action.destructive.hover-textobserveduse | #f9faf9 | Destructive button text on hover (4.58:1). |
destructive.bg-active | color.action.destructive.pressed-bgobserveduse | #ae1914 | Destructive button pressed fill. |
destructive.bg-filled | color.action.destructive.filled-bgobserveduse | #dc282e | Confirm-dialog destructive fill. |
destructive.text-filled | color.action.destructive.filled-textobserveduse | #f9faf9 | Text on the destructive fill. |
root.border-active | color.action.primary.borderobserveduse | #9e231f | Primary button border. |
secondary.border-active | color.border.activeobserveduse | #e53935 | Active or focused control border. |
tertiary.border-active | color.border.activeobserveduse | #e53935 | Active or focused control border. |
destructive.border-active | color.action.destructive.borderobserveduse | #e53935 | Destructive button border. |
root.text-disabled | color.text.disabledapproveduse | #a3676b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
root.bg-disabled | color.interaction.disabled.bgapproveduse | #0d0707 | D-041 interaction.disabled.bg: color.primitive.modern.panel. |
root.border-disabled | color.border.disabledapproveduse | #482b2b | D-041 border.disabled: color.primitive.modern.divider. |
loading.glyph | color.action.primary.textapproveduse | #ffa2a7 | Primary action labels: bright rose across rest, hover and pressed states (D-033). |
Purpose
Triggers an action; navigation is a link. Four tones only, and one primary
per view; secondary serves every other action, tertiary low-emphasis and
toolbar actions. A destructive button also needs a confirmation step or an
undo; the colour is not the safeguard.
Anatomy
The filled destructive modifier (.button-destructive-filled,
color.action.destructive.filled-bg with
color.action.destructive.filled-text) exists only inside confirm dialogs.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | primary: color.action.primary.bg fill, color.action.primary.text, 1px color.action.primary.border; secondary: 1px color.action.secondary.border, color.action.secondary.text; tertiary: color.action.tertiary.text, no border; destructive: 1px color.action.destructive.border, color.action.destructive.text | — |
| hover | primary → color.action.primary.hover-bg; secondary → color.action.secondary.hover-bg; tertiary → color.action.tertiary.hover-bg; destructive fills color.action.destructive.hover-bg with color.action.destructive.hover-text | cursor: pointer; hover-capable pointers only |
| focus-visible | 1px dashed ring at −2px: color.interaction.focus.ring-container on the primary fill, color.interaction.focus.ring on the outline tones, color.action.destructive.hover-text on the destructive hover fill | the ring |
| active | fill: primary color.action.primary.pressed-bg, secondary color.action.secondary.pressed-bg, tertiary color.interaction.pressed.bg, destructive color.action.destructive.pressed-bg; border: primary and destructive keep theirs, secondary and tertiary take color.border.active (D-015) | border change on secondary and tertiary; no translation or scale |
| disabled | color.text.disabled on color.interaction.disabled.bg, 1px color.border.disabled, every tone alike; no hover or active styling | disabled; cursor: not-allowed; never opacity |
| loading | size and tone unchanged; the static ⋯ glyph replaces the icon; stays focusable, ignores activation | glyph; aria-busy |
Precedence: disabled > loading > active > hover; focus-visible is always drawn.
Keyboard
Native; nothing else is bound. A loading button keeps its tab stop so focus is not lost mid-action.
Accessibility
The accessible name is the label; an icon-only button is icon-button.
Never aria-disabled on a button that should be skipped. When a user needs
to discover why an action is unavailable, keep the button enabled and
explain on activation instead of disabling it. Text meets 4.5:1 and borders
and rings 3:1 on every fill they sit on. Target at least 24×24 CSS pixels in
compact, with 4px between neighbours.
Portability
Fill, border and outline express every state; no gradient, shadow or transform. A host without dashed outlines draws a solid 1px ring in the same colour and records the deviation; one that cannot recolour the ring on a fill draws it outside the fill at +1px.
Non-examples
Rounded or pill shapes. Two filled buttons in one view. A red filled
destructive button at rest. A hover that only changes text colour.
Translating or scaling on press. A spinner inside the button. Disabling with
opacity. A <div> or <a> styled as a button. A gradient or shadow for
depth.
Contrast report
25 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 1 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| button default text | default | text | color.action.primary.text #ffa2a7 | color.action.primary.bg #7d1310 | 5.57 | 4.5 | pass |
| button hover text | hover | text | color.action.primary.text #ffa2a7 | color.action.primary.hover-bg #911410 | 4.75 | 4.5 | pass |
| button focus-visible text | focus-visible | text | color.action.primary.text #ffa2a7 | color.action.primary.bg #7d1310 | 5.57 | 4.5 | pass |
| button focus-visible outline | focus-visible | ui | color.interaction.focus.ring-container #ffa2a7 | color.action.primary.bg #7d1310 | 5.57 | 3 | pass |
| button active text | active | text | color.action.primary.text #ffa2a7 | color.action.primary.pressed-bg #630f0d | 6.84 | 4.5 | pass |
| button loading text | loading | text | color.action.primary.text #ffa2a7 | color.action.primary.bg #7d1310 | 5.57 | 4.5 | pass |
| primary fill against the panel (decorative; the label identifies the button) | — | ui | color.action.primary.bg #7d1310 | color.surface.default #0d0707 | 1.88 | 3 | waived: the fill is not the boundary of the control; the label text identifies it, and hover, active and focus each add a second channel |
| secondary text at rest | — | text | color.action.secondary.text #ffa2a7 | color.action.secondary.bg #000000 | 10.98 | 4.5 | pass |
| secondary border at rest | — | ui | color.action.secondary.border #986f72 | color.action.secondary.bg #000000 | 4.85 | 3 | pass |
| secondary text on the hover fill | hover | text | color.action.secondary.text #ffa2a7 | color.action.secondary.hover-bg #630f0d | 6.84 | 4.5 | pass |
| secondary border against the panel while hovered (the fill inside it measures 2.94:1) | hover | ui | color.action.secondary.border #986f72 | color.surface.default #0d0707 | 4.62 | 3 | pass |
| ring on the secondary hover fill | hover | ui | color.interaction.focus.ring #e53935 | color.action.secondary.hover-bg #630f0d | 3.10 | 3 | pass |
| secondary text while pressed | active | text | color.action.secondary.text #ffa2a7 | color.action.secondary.pressed-bg #420f0c | 8.41 | 4.5 | pass |
| tertiary text at rest | — | text | color.action.tertiary.text #e99499 | color.action.secondary.bg #000000 | 9.16 | 4.5 | pass |
| tertiary text on the hover fill | hover | text | color.action.tertiary.text #e99499 | color.action.tertiary.hover-bg #1c0a09 | 8.35 | 4.5 | pass |
| tertiary text while pressed | active | text | color.action.tertiary.text #e99499 | color.interaction.pressed.bg #420f0c | 7.02 | 4.5 | pass |
| active border on the pressed fill | active | ui | color.border.active #e53935 | color.interaction.pressed.bg #420f0c | 3.81 | 3 | pass |
| destructive text at rest | — | text | color.action.destructive.text #f07178 | color.action.secondary.bg #000000 | 7.34 | 4.5 | pass |
| destructive border at rest | — | ui | color.action.destructive.border #e53935 | color.action.secondary.bg #000000 | 4.97 | 3 | pass |
| destructive text on the hover fill | hover | text | color.action.destructive.hover-text #f9faf9 | color.action.destructive.hover-bg #dc282e | 4.58 | 4.5 | pass |
| ring on the destructive hover fill (the ring takes the on-fill colour) | hover | ui | color.action.destructive.hover-text #f9faf9 | color.action.destructive.hover-bg #dc282e | 4.58 | 3 | pass |
| destructive text while pressed | active | text | color.action.destructive.hover-text #f9faf9 | color.action.destructive.pressed-bg #ae1914 | 6.81 | 4.5 | pass |
| confirm-dialog filled destructive | — | text | color.action.destructive.filled-text #f9faf9 | color.action.destructive.filled-bg #dc282e | 4.58 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | color.text.disabled #a3676b | color.interaction.disabled.bg #0d0707 | 4.48 | 3 | pass |
| disabled border (exempt) | disabled | ui | color.border.disabled #482b2b | color.interaction.disabled.bg #0d0707 | 1.58 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab / Shift+Tab | Moves focus to and from the button in document order. |
| Enter / Space | Activates (native); Space activates on key up. |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: icon-button link button-group segmented-control dialog
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-TOUCH FX-DENSITY FX-STATE-MATRIX