The control. Every state in spec 1.11 that applies to a button, in both schemes. A control is one row tall (--fj-row), because DENS-1 makes the single-line row the system's unit and a control that is taller than a row of data would be the loudest thing on a dense page.
Three variants. Outline is the default.
Pointer only. The surface lifts and the label steps up one rung; nothing moves, because a hover that changes a dimension moves everything after it (STATE-HOVER).
--fj-ring-width solid --fj-ring at --fj-ring-offset, square corners regardless of the control's own radius. :focus-visible only, so a mouse click does not ring. `outline: none` appears nowhere in the system (STATE-FOCUS-1..3, AP-8).
The background deepens one surface step. No transform, no scale (AP-9).
Text to --fj-faint, border to --fj-rule, cursor not-allowed, no hover response. --fj-faint clears 4.5:1 on every surface it is permitted to sit on: disabled is not an excuse to stop being legible (STATE-DISABLED).
aria-busy, the label reads a word, and the control is inert so an in-flight action cannot be started twice. No spinner, no shimmer, no pulse: a shimmer is ambient motion wearing a loading costume (MOT-3, AP-9).
A control does not wrap. It grows, and the layout around it decides.
button_class() gives an <a> the same look, which is the only element other than <button> that ever carries it. The ring comes from the component's own rule rather than from the base layer, so a linked control rings identically.
Anything the caller passes reaches the element, and a class among it is APPENDED to the component's rather than replacing it. The appended utility wins, because the component layer is @layer components and a utility is @layer utilities.
The escape hatch: the element, the structure and the aria wiring, without the fj-* classes. For the case where the system is wrong about the look and right about the behaviour. Two call sites reaching for it means the component is wrong.
Three variants. Outline is the default.
Pointer only. The surface lifts and the label steps up one rung; nothing moves, because a hover that changes a dimension moves everything after it (STATE-HOVER).
--fj-ring-width solid --fj-ring at --fj-ring-offset, square corners regardless of the control's own radius. :focus-visible only, so a mouse click does not ring. `outline: none` appears nowhere in the system (STATE-FOCUS-1..3, AP-8).
The background deepens one surface step. No transform, no scale (AP-9).
Text to --fj-faint, border to --fj-rule, cursor not-allowed, no hover response. --fj-faint clears 4.5:1 on every surface it is permitted to sit on: disabled is not an excuse to stop being legible (STATE-DISABLED).
aria-busy, the label reads a word, and the control is inert so an in-flight action cannot be started twice. No spinner, no shimmer, no pulse: a shimmer is ambient motion wearing a loading costume (MOT-3, AP-9).
A control does not wrap. It grows, and the layout around it decides.
button_class() gives an <a> the same look, which is the only element other than <button> that ever carries it. The ring comes from the component's own rule rather than from the base layer, so a linked control rings identically.
Anything the caller passes reaches the element, and a class among it is APPENDED to the component's rather than replacing it. The appended utility wins, because the component layer is @layer components and a utility is @layer utilities.
The escape hatch: the element, the structure and the aria wiring, without the fj-* classes. For the case where the system is wrong about the look and right about the behaviour. Two call sites reaching for it means the component is wrong.