j3w1 / ButtonVue · HTML · React · Astro

J3W1 / DESIGN SYSTEM

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 · 1.1.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@1.1.0
import '@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.

Vue SFC, React, Astro and plain HTML integration →

Copy the complete implementation

npx --package=@j3w1/ui@1.1.0 j3w1-ui copy button --out ./vendor/j3w1/button

The 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

NameType / signatureMeaning
disabledCustomEventDisable the native controls owned by this instance.
loadingCustomEventMark the primary control busy and prevent duplicate activation.
toneCustomEventButton tone: primary, secondary, tertiary or destructive.

properties

NameType / signatureMeaning
disabledbooleanDelegates disabled state to native children.
namestringNative control name; application form keys remain explicit.

methods

NameType / signatureMeaning
refresh(): voidReconnect behavior after replacing native child structure.
focus(options?: FocusOptions): voidFocus the first usable native control.

events

NameType / signatureMeaning
j3w1-actionCustomEventA 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

Purpose

The push button that triggers an action. Four tones and no more: primary is the single filled button in a view, secondary is the outline for every other action, tertiary is the ghost for low-emphasis and toolbar actions, destructive is an outline in the danger tone that fills on hover. A destructive button also requires a confirmation step or an undo affordance; the colour is not the safeguard. Navigation is a link, not a button.

Anatomy

A native <button>; optional leading icon; the label. The loading glyph occupies the icon slot so the size never changes. A filled destructive modifier (.button-destructive-filled, color.action.destructive.filled-bg with color.action.destructive.filled-text) exists only inside confirm dialogs.

States

StateVisualNon-colour channel
defaultprimary: 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
hoverprimary → 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-textcursor: pointer; hover-capable pointers only
focus-visiblering 1px dashed 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 fillthe ring
activeprimary → color.action.primary.pressed-bg; secondary → color.action.secondary.pressed-bg; tertiary → color.interaction.pressed.bg; destructive → color.action.destructive.pressed-bg; primary keeps color.action.primary.border, secondary and tertiary use color.border.active, destructive keeps color.action.destructive.border (D-015)border change on secondary and tertiary; no translation or scale
disabledcolor.text.disabled on color.interaction.disabled.bg with 1px color.border.disabled, every tone alike; no hover or active stylingdisabled; cursor: not-allowed; never opacity
loadingsize unchanged; the static glyph replaces the icon; the button keeps its tone and stays focusable but ignores activationglyph; aria-busy

Precedence: disabled > loading > active > hover; focus-visible is always drawn.

The primary edge stays unchanged when pressed (D-015). Its exact role is root.border-active in the part map, just as root.border names the rest edge. The default-tone state contrast map checks the text against the pressed fill; it does not substitute color.border.active for this unchanged edge. The existing primary-action contrast declaration identifies the labelled button through its text, not through the fill’s boundary.

Keyboard

Native. Enter and Space activate; nothing else is bound. A loading button stays in the tab order so focus is not lost mid-action, and the host ignores activation until aria-busy clears.

Accessibility

The accessible name is the label text; an icon-only button is the icon-button component. Never aria-disabled on a button that should be skipped, and never disabled on a button whose reason a user needs to discover: prefer keeping it enabled and explaining on activation. Contrast: primary 9.28:1, secondary 10.37:1, tertiary 8.65:1, destructive 5.40:1 at rest and 4.58:1 on the hover fill; every outline border ≥ 4.45:1; rings ≥ 3:1 on every fill they sit on. The target is at least 24×24 CSS pixels in compact density with 4px between neighbours.

Portability

Fill, border and outline express every state; no gradient, shadow or transform is used. Hosts without dashed outlines draw a solid 1px ring in the same colour and record the deviation. Hosts that cannot recolour the ring on a fill draw it outside the fill at +1px.

Non-examples

Rounded corners 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 the button on press. A spinner inside the button. Disabling with opacity. A <div> or <a> styled as a button. A gradient or shadow to suggest depth.

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • DEMO Vue application demo
  • SPEC Complete UI theme specification