6.0 KiB
Button
Single-press action surface — a native <button> with loading / disabled
state, a click flow gated on both, and the contact-activate perceptual
event. Soma owns behavior; the eidos wrapper layers icons + visual recipes
on top. (Links are <Link>'s job — Button has no href form; compose via
the child snippet when another element must carry the behavior. That is
how a navigating call-to-action gets the button's paint: eidos README §«CTA
que navega».)
Anatomy
<Button.Provider color="primary" onclick={save}>
Save
</Button.Provider>
<Button.Provider intent="threat" loading={isDeleting} loadingText="Deleting…">
Delete
</Button.Provider>
Parts
| Part | Element | Description |
|---|---|---|
Provider |
<button> |
Root. Native button; Enter/Space activate natively. child for asChild. |
Spinner |
<span> |
Loading indicator. Auto-rendered by the Provider while loading; render your own for full control. |
Props
Provider
| Prop | Type | Default | Description |
|---|---|---|---|
intent |
'neutral' | 'affirm' | 'fulfill' | 'risk' | 'threat' | 'loss' |
'neutral' |
Visual evaluative tint (chip + data-color). Does NOT load the sema event — see below. |
color |
'primary' | 'secondary' | 'tertiary' | 'neutral' | (string & {}) |
'primary' visual |
Hierarchical accent; only applies under intent='neutral'. Any donor palette scale allowed (THEMING §25). |
type |
'button' | 'submit' | 'reset' |
'button' |
Native button type (default avoids implicit form submit). Dropped in the child form — the element is the consumer's, so type travels with it. |
disabled |
boolean |
false |
OR-merged with Field.Provider. Click flow gated. |
loading |
boolean |
false |
Gates the click flow (no contact-activate), sets aria-busy, renders the Spinner. |
loadingText |
string |
— | Localised swap for children while loading (children stay sr-only). |
spinnerPlacement |
'start' | 'end' |
'start' |
Where the spinner renders relative to children. |
aria-label |
string |
— | Accessible-name override; required when the button has no visible text. |
onPress |
OnChangeFn<{ intent: ButtonIntent }> |
— | Diagnostic feedback that contact-activate fired; the side effect is onclick. |
onclick |
(e: MouseEvent) => void |
— | Click handler. Standard DOM event. |
Snippet props (children / child): { loading, disabled }. The eidos
<Button> adds content to child — its decorated body — so an <a> that
looks like a button keeps the icon / label / spinner slots.
ARIA & data attributes
| Attribute | Value |
|---|---|
aria-busy |
"true" while loading |
aria-disabled |
"true" while disabled |
aria-label |
From the aria-label prop when set |
data-color |
Resolved visual color (intent wins when evaluative) |
data-loading |
Presence flag while loading |
data-disabled |
Presence flag while disabled |
No data-state: the enum was removed after it clobbered host disclosures'
data-state="open|closed" when Button chromed their triggers (audit
2026-05-27) — binary states are presence flags.
Sema events
| Event | Family | Verb | When |
|---|---|---|---|
contact-activate |
contact |
activate |
Click handler invoked (not while disabled/pending). |
Per book cap. 22 §10 ("Botón: contact.press"), the Button's morfo
event is just the reception of the user's gesture — pre-evaluation,
no intent on the event itself. The intent prop drives visual weight
(data-color, chip variant) but does NOT load the contact event with
threat/loss/etc. tonality. For the evaluative consequence (the actual
save sound, the delete haptic), consumers fire downstream semantic
events when the real outcome lands:
<Button intent="threat" onclick={confirmDelete}>Delete</Button>
<!--
contact-activate at click → generic contact tick
signal.warn + threat → consumer fires before destructive op
commit.delete + loss → consumer fires after destructive op completes
-->
This way the Button stops "celebrating before time" (book cap. 22 §8) — the celebration sound plays at the moment the save actually resolves, not when the click is received.
See also
- Eidos visual:
src/uix/eidos/components/button/README.md - Button consumer pattern: every Trigger / Action / Cancel / ClearButton renders
<Button>via the somachildsnippet (DropdownMenu.Trigger, AlertDialog.Action, SearchField.ClearTrigger, etc.).