Skip to content

cv-button ​

Interactive element that triggers an action or toggles a pressed state.

Headless: createButton

Usage ​

View source
html
<div class="button-demo-shell" data-demo="button" data-live-demo-height="780">
  <section class="button-demo-hero" aria-labelledby="button-demo-title">
    <div class="button-demo-copy">
      <span class="button-demo-kicker">Action primitive</span>
      <h3 id="button-demo-title">
        Use button when a user commits intent, changes mode, or starts a safe action.
      </h3>
      <p>
        The component keeps activation, toggle, disabled, loading, and form behavior in the headless button
        model. UIKit owns only the action surface, slots, variants, and sizing.
      </p>
    </div>

    <dl class="button-demo-metrics" aria-label="Button contract summary">
      <div>
        <dt>Variants</dt>
        <dd>default / primary / danger / ghost</dd>
      </div>
      <div>
        <dt>States</dt>
        <dd>disabled / loading / pressed</dd>
      </div>
      <div>
        <dt>Slots</dt>
        <dd>prefix + suffix</dd>
      </div>
    </dl>
  </section>

  <section class="button-demo-board" aria-label="Button examples in a vault workflow">
    <div class="button-demo-command">
      <div class="button-demo-command-head">
        <div>
          <span>Visible vault route</span>
          <strong>travel-profile.visible</strong>
        </div>
        <span class="button-demo-status">safe action window</span>
      </div>

      <div class="button-demo-layer">
        <span>Operator decision</span>
        <p>Commit the visible profile, inspect routing proof, or block relay writes before export.</p>
      </div>

      <div class="button-demo-actions" aria-label="Primary action examples">
        <cv-button preset="action-primary">
          <svg class="button-demo-icon" slot="prefix" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M20 6 9 17l-5-5"></path>
          </svg>
          Commit visible profile
        </cv-button>

        <cv-button outline>
          Inspect route
          <svg class="button-demo-icon" slot="suffix" viewBox="0 0 24 24" aria-hidden="true">
            <path d="m9 18 6-6-6-6"></path>
          </svg>
        </cv-button>

        <cv-button variant="ghost" outline>
          <svg class="button-demo-icon" slot="prefix" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M12 5v14"></path>
            <path d="M5 12h14"></path>
          </svg>
          Export proof
        </cv-button>

        <cv-button variant="danger" outline>
          <svg class="button-demo-icon" slot="prefix" viewBox="0 0 24 24" aria-hidden="true">
            <path d="M18 6 6 18"></path>
            <path d="m6 6 12 12"></path>
          </svg>
          Disable relay
        </cv-button>
      </div>
    </div>

    <aside class="button-demo-side" aria-label="Button state examples">
      <div class="button-demo-side-head">
        <span class="button-demo-kicker">State map</span>
        <h4>Each state changes affordance without changing the contract.</h4>
      </div>

      <div class="button-demo-state-list">
        <div>
          <span>Loading blocks action</span>
          <cv-button variant="primary" loading>Committing</cv-button>
        </div>
        <div>
          <span>Disabled stays visible</span>
          <cv-button disabled>Unavailable</cv-button>
        </div>
        <div>
          <span>Toggle reflects pressed</span>
          <cv-button toggle pressed>Policy pinned</cv-button>
        </div>
      </div>
    </aside>
  </section>

  <section class="button-demo-section" aria-labelledby="button-demo-matrix-title">
    <div class="button-demo-section-header">
      <span class="button-demo-kicker">Variants, sizes, and shape</span>
      <h4 id="button-demo-matrix-title">
        Use one action hierarchy, then tune emphasis with variant and modifier.
      </h4>
    </div>

    <div class="button-demo-matrix" aria-label="Button variant and size matrix">
      <div>
        <span>Default</span>
        <cv-button>Review policy</cv-button>
        <cv-button outline>Quiet secondary</cv-button>
      </div>
      <div>
        <span>Primary</span>
        <cv-button variant="primary">Save trust route</cv-button>
        <cv-button variant="primary" outline>Primary outline</cv-button>
      </div>
      <div>
        <span>Danger</span>
        <cv-button variant="danger">Block export</cv-button>
        <cv-button variant="danger" outline>Danger outline</cv-button>
      </div>
      <div>
        <span>Ghost</span>
        <cv-button variant="ghost">Open details</cv-button>
        <cv-button variant="ghost" outline>Ghost outline</cv-button>
      </div>
      <div>
        <span>Size</span>
        <cv-button size="small">Small</cv-button>
        <cv-button>Medium</cv-button>
        <cv-button size="large">Large</cv-button>
      </div>
      <div>
        <span>Shape and preset</span>
        <cv-button pill>Rounded action</cv-button>
        <cv-button preset="action-primary-subtle">Subtle primary</cv-button>
      </div>
    </div>
  </section>
</div>

Anatomy ​

<cv-button> (host)
└── <button part="base" type="button" role="button">
    ├── <span part="spinner" aria-hidden="true">   ← only when [loading]
    ├── <span part="prefix">
    │   └── <slot name="prefix">
    ├── <span part="label">
    │   └── <slot>
    └── <span part="suffix">
        └── <slot name="suffix">

Attributes ​

AttributeTypeDefaultDescription
disabledBooleanfalsePrevents interaction
toggleBooleanfalseEnables toggle (pressed) behavior
pressedBooleanfalsePressed state (only meaningful when toggle is true)
loadingBooleanfalseShows spinner and blocks interaction
variantString"default"Visual variant: default | primary | danger | ghost
presetString—Semantic preset: action-primary | action-primary-subtle
outlineBooleanfalseOutlined appearance (transparent background, visible border)
pillBooleanfalseFully rounded edges (border-radius: 999px)
sizeString"medium"Size: small | medium | large
typeString"button"Form action type: button | submit | reset

Form Behavior ​

  • type="button": no form action.
  • type="submit": triggers form submission for nearest form owner.
  • type="reset": triggers form reset for nearest form owner.
  • Form owner resolution order:
    1. host [form="..."] attribute by element id
    2. nearest ancestor form via closest('form')
  • If disabled or loading is active, submit/reset actions are blocked.

Variants ​

VariantDescription
defaultDefault filled style with surface background and border
primaryPrimary-tinted background and border using --cv-color-primary
dangerDanger-tinted background and border using --cv-color-danger
ghostTransparent background and border

The outline boolean modifier can be combined with any variant to produce an outlined appearance (transparent background, visible border tinted by variant color).

Presets ​

PresetDescription
action-primaryStrong primary action surface for form/footer actions
action-primary-subtleSofter primary action surface for inline edit actions

When a preset is combined with variant, the semantic preset owns the button surface and foreground states.

Sizes ​

Size--cv-button-min-height--cv-button-padding-inline--cv-button-padding-block--cv-button-font-size
small30pxvar(--cv-space-2, 8px)var(--cv-space-1, 4px)var(--cv-font-size-sm, 13px)
medium36pxvar(--cv-space-3, 12px)var(--cv-space-2, 8px)var(--cv-font-size-base, 14px)
large42pxvar(--cv-space-4, 16px)var(--cv-space-2, 8px)var(--cv-font-size-md, 16px)

Slots ​

SlotDescription
(default)Button label
prefixIcon or element before label
suffixIcon or element after label

CSS Parts ​

PartElementDescription
base<button>Root interactive element with role="button"
label<span>Wrapper around the default slot
prefix<span>Wrapper around the prefix slot
suffix<span>Wrapper around the suffix slot
spinner<span>Loading spinner (rendered only when loading is true)

CSS Custom Properties ​

PropertyDefaultDescription
--cv-button-min-height36pxMinimum block size of the button
--cv-button-padding-inlinevar(--cv-space-3, 12px)Horizontal padding
--cv-button-padding-blockvar(--cv-space-2, 8px)Vertical padding
--cv-button-border-radiusvar(--cv-radius-sm, 6px)Border radius for button shape
--cv-button-gapvar(--cv-space-2, 8px)Spacing between button content parts
--cv-button-font-sizevar(--cv-font-size-base, 14px)Font size of button content
--cv-button-font-weightinheritFont weight of button content
--cv-button-text-colorvar(--cv-color-text)Foreground color
--cv-button-backgroundvar(--cv-color-surface)Default background
--cv-button-background-hoverderived from the active variantHover background
--cv-button-background-activederived from the active variantActive background
--cv-button-border-colorvar(--cv-color-border)Default border color
--cv-button-border-color-hoverderived from the active variantHover border color
--cv-button-border-color-activederived from the active variantActive border color
--cv-button-focus-ring-colorcurrent variant accentFocus ring color
--cv-button-action-primary-backgroundtheme-specific primary action surfaceaction-primary background
--cv-button-action-primary-background-hovertheme-specific primary action surfaceaction-primary hover background
--cv-button-action-primary-background-activetheme-specific primary action surfaceaction-primary active background

Additionally, component styles depend on theme tokens through fallback values:

Theme PropertyDefaultDescription
--cv-color-border#2a3245Base border color
--cv-color-surface#141923Surface background color
--cv-color-text#e8ecf6Default text color
--cv-color-primary#65d7ffPrimary accent color
--cv-color-danger#ff7d86Danger accent color
--cv-duration-fast120msTransition duration
--cv-easing-standardeaseTransition timing function
--cv-radius-sm6pxBase radius used for button fallback
--cv-space-14pxSmall spacing scale fallback
--cv-space-28pxMedium spacing scale fallback
--cv-space-312pxMedium-large spacing scale fallback
--cv-space-416pxLarge spacing scale fallback

Visual States ​

Host selectorDescription
:host([disabled])Reduced opacity (0.55), cursor: not-allowed
:host([pressed])Primary-tinted background (per variant)
:host([loading])Shows spinner, label reduced opacity (0.72), cursor: progress
:host([variant="default"])Default surface background with border
:host([variant="primary"])Primary-tinted background and border
:host([variant="danger"])Danger-tinted background and border
:host([variant="ghost"])Transparent background and border
:host([outline])Transparent background, visible border (tinted by variant)
:host([pill])Fully rounded edges
:host([size="small"])Small size overrides
:host([size="large"])Large size overrides
  • In prefers-reduced-motion: reduce, the loading spinner remains visible but slows from 800ms to 3200ms.

Reactive State Mapping ​

cv-button is a visual adapter over headless createButton.

UIKit PropertyDirectionHeadless Binding
disabledattr → actionactions.setDisabled(value)
loadingattr → actionactions.setLoading(value)
pressedattr → actionactions.setPressed(value)
toggleattr → optionpassed as isPressed presence in createButton(options)
Headless StateDirectionDOM Reflection
state.isDisabled()state → attr[disabled] host attribute
state.isLoading()state → attr[loading] host attribute
state.isPressed()state → attr[pressed] host attribute
  • contracts.getButtonProps() is spread onto the inner [part="base"] element to apply role, aria-disabled, aria-busy, aria-pressed, tabindex, and keyboard/click handlers.
  • UIKit dispatches cv-input and cv-change events by observing isPressed changes triggered by user activation (not by controlled setPressed).
  • UIKit does not own activation or toggle logic; headless state is the source of truth.

Events ​

EventDetailDescription
cv-input{pressed: boolean, toggle: boolean}Fires on activation in toggle mode only
cv-change{pressed: boolean}Fires when pressed state changes (toggle mode only)

Normal (non-toggle) buttons rely on the native click event.

Legacy Button Migration Mapping ​

Variant mapping ​

  • brand -> primary
  • neutral -> default
  • secondary -> default
  • plain -> ghost
  • text -> ghost
  • primary -> primary
  • danger -> danger
  • ghost -> ghost

Slot mapping ​

  • slot="start" -> slot="prefix"
  • slot="end" -> slot="suffix"

ChromVoid UIKit documentation