Skip to content

Buttons ​

The button family covers every clickable affordance in vueda: primary buttons, button groups, toggles, switches, and the keyboard caps that pair with them. All members share the same control sizing (32 / 28 / 40, governed by --vueda-control-height and its sm / lg companions) and focus treatment (2 px solid --ring outline at 2 px offset).

This page is the visual contract the default theme guarantees. Use it as the target spec when you re-skin: every cell shown here should still read as the same control after a customization, even if its color, radius, or density shifts. If a cell breaks, the change has crossed from skin into design language.

For the mechanics of overriding any of this, see Customize VUEDA Appearance. In brief: values (color, dimension, duration) belong in CSS tokens; compositions (class arrangements, state recipes) belong in theme keys.

Button: state matrix ​

Every tone × emphasis × size × state the default theme renders. The matrix below exercises the same Tailwind utilities the real component does, with hover and focus-visible reproduced via the docs harness so every cell is verifiable without pointer or keyboard.

Theme keys: Button, composing _ButtonBase plus the _Button* primitive for the resolved (tone, emphasis) cell (see Tone × emphasis below). To restyle every button-shaped surface in the app (calendar day cells, pagination items, dialog actions), override the matching _Button* meta key rather than Button itself.

Button: tone × emphasis ​

Button resolves on two axes: tone (neutral · primary · destructive, the color) and emphasis (fill · outline · ghost · link, the structure). A bare <Button> is neutral fill. The primary CTA is explicit with tone="primary", and secondary structure is explicit with emphasis. The grid below drives each cell from the two-axis props directly.

Theme keys: each cell composes _ButtonBase plus its tone primitive: fills are _ButtonDefault / _ButtonSecondary / _ButtonDestructive; outlines _ButtonOutline / _ButtonPrimaryOutline / _ButtonDestructiveOutline; ghosts _ButtonGhost / _ButtonPrimaryGhost / _ButtonDestructiveGhost; links _ButtonNeutralLink / _ButtonLink / _ButtonDestructiveLink.

Choosing tone and emphasis ​

The two axes answer two independent questions, and the default theme keeps them independent. Decide each separately:

  • emphasis is placement. How loud the control is and what chrome it sits in. It does not depend on what the action does.
  • tone is meaning. What the action is. A delete reads destructive whether it is a page hero or a quiet row glyph; the tone is set once and does not change as the same action moves between contexts.
  • primary is "the one action here." Exactly one control per context earns a fill. Promotion sets emphasis to fill and lifts a neutral action's tone to primary; a promoted destructive action stays a destructive fill.

Pick emphasis by placement ​

  • fill is the single earned action in a context: the form submit, the confirm in a dialog, the hero action in a page title. One per context.
  • outline is a genuine alternative that still deserves a chip: a secondary form action, a page-title secondary action, a toolbar trigger, pagination, an error-recovery retry.
  • ghost is a dismiss or a dense-strip action: cancel, clear, the actions in a bulk-selection bar, an inline tertiary affordance. Pair a ghost action with an icon or place it in an established action group so it reads as interactive before hover. For a standalone text-only action, prefer outline. The list's Clear filters and Reset sort controls use small neutral outlines: they change the view's constraints, so they do not need destructive tone.
  • link is inline within running prose only. Never in a toolbar or an action strip, where it breaks the control rhythm.

Pick tone by meaning ​

  • neutral is the resting default. A bare <Button> is a neutral fill, not a CTA.
  • primary is the earned accent. Reach for it only on the one promoted action per context (or set it explicitly for a deliberate CTA). Spreading primary across a cluster spends the accent that signals "the" action.
  • destructive marks deletion, including removal of an unsaved inline row, or a repeated value through FieldSetMany. Removing a draft value deserves the same tone even though it has not been saved. Clearing a filter changes the view rather than its data, so that control remains neutral. Set the tone, not a text-destructive class: a destructive ghost (tone="destructive" emphasis="ghost") is a quiet red row action, a destructive fill is a confirm hero. The tone composes the right _ButtonDestructive* primitive for whatever emphasis the placement chose.

Size by chrome density ​

Size is a third, independent placement axis: sm in dense chrome (titles, toolbars, dialogs, bulk bars), default (32px) in form footers, lg for auth and full-page CTAs, icon-sm for pagination.

ViewList supplies size="sm" for bulk and workflow actions through their shared slot props. Spread those props in a bulk-action-button or workflow-action-button replacement to retain the placement size; override size explicitly when your custom layout needs a different control height.

Placement reference ​

PlacementPrimary actionAlternativeDismissDestructive
Page-title action zoneprimary fill, smneutral outline, smghost smdestructive fill, sm
Form / dialog footerprimary fillneutral outlineghostdestructive fill
Toolbarneutral outline, sm
Inline fieldset rowneutral outline, sm (Create)destructive ghost, sm (icon + Delete)
Bulk-selection barghost smdestructive ghost, sm
Paginationneutral outline, icon-sm
Empty stateprimary fill, smneutral outline, smghost sm
Inline (running prose)primary linkneutral linkdestructive link

For buttons that navigate to a model action, this resolution happens automatically: every action button renders through Button via LinkModelView, which reads the action's intrinsic tone (a delete / destroy action is destructive, everything else neutral), applies the placement emphasis chosen by the surrounding view, and promotes the view's hero action to a fill. Authoring a <Button> by hand should follow the same table so hand-placed and resolved buttons read identically.

Custom inline row actions ​

An inline action entry in fieldObjects uses a label and an action function, for example { fieldName: "inspect", label: "Inspect", action: inspectRow }. The default non-destroy action button is a small neutral outline button in both stacked and tabular layouts. Both use FieldSetInlineActionButton, which owns the styling and invokes the callback without submitting the parent form.

The callback receives action, fieldSetContextState, rowValueName, and the click event. rowValueName identifies the current value path, such as lines[1]; a singular stacked inline uses its field name without an index. Stacked passes its existing inline state (field objects, actions, and selection), while tabular passes its field context state (including the field value) and also supplies objectGridFieldSlotProps and doCreate. These context objects are layout-specific; do not assume they have the same shape.

An item-action-button slot replaces the default button and owns its activation handler. Use the slot's action and row context when implementing that handler.

Customizing built-in inline buttons ​

Stacked, singular stacked, and tabular inlines use a small destructive ghost button with a trash icon and a visible Delete label for unsaved rows. Create uses a small neutral outline button. The icon is decorative; the label provides the button's accessible name.

The inline component selects tone, emphasis, and size. useTheme resolves the classes that draw those choices. Choose the customization point by what you need to change:

  • For layout or extra classes on Delete, override FieldSetStackedInlineRow.destroyButton (also used by singular stacked inlines) or FieldSetTabularInline.destroyButton.
  • For shared button appearance, override Button.root or its composed primitives. A themeOverride on the fieldset scopes overrides to that fieldset and its descendants; a Button override there affects Create and other buttons too.
  • For the trash icon, supply iconOverride with the typeDeleted entry under FieldSetStackedInlineRow or FieldSetTabularInline. Default.typeDeleted changes the shared fallback.
  • To choose different button props or replace its content, use the fieldset's destroy-button slot. Forward its click handler so the replacement still removes the unsaved row. Theme class hooks do not change component props.

ButtonGroup: composition matrix ​

ButtonGroup adjusts its children's radii and shared edges so adjacent buttons read as a single control. The interesting axis is composition: orientation, child variant, and what kind of children sit inside (buttons, static text, separators). Child-button state shifts (hover, focus, disabled) are already covered by the Button matrix above.

Theme keys: ButtonGroup, ButtonGroupText, ButtonGroupSeparator. The seam treatment is a child-radius and hairline-overlap override applied via the parent's data attributes, so re-skinning the seam means editing these keys, not the token layer.

Toggle: state matrix ​

Toggle adds a pressed axis on top of Button's tone × emphasis × size × state cube. Each cell shows the size row and state row twice: once unpressed, once pressed, so the press delta is visible against every other state.

Theme key: Toggle. The pressed surface reads from --accent / --accent-foreground, the same pair that drives hover on ghost and outline buttons; rebranding the accent shifts all three in lockstep.

Disabled toggles use --disabled-foreground ink at full opacity. A pressed toggle keeps a --disabled fill; an unpressed toggle stays transparent. Outline variants soften their edge to --border. The public pressed, defaultPressed, and update:pressed API controls this state, including through v-model:pressed.

ToggleGroup: composition matrix ​

ToggleGroup's interesting axis is composition (selection mode, spacing, child variant), not state. Per-item states are covered by the Toggle matrix above.

Theme keys: ToggleGroup, ToggleGroupItem. Items have their own theme recipe and match Toggle's state treatment. Customize both keys when restyling the whole family. Disabling the group applies disabled colors to every item while preserving the selected values and joined outline seams.

Switch: state matrix ​

Switch has no variant or size axis in the current skin, so the matrix collapses to off | on × default | hover | focus-visible | disabled.

Theme key: Switch. Token surface: --input (off track), --primary (on track), --background (thumb), --ring (focus). Switch is the one place a brand's --primary reads as a fill rather than as a CTA; verify the on-track contrast against the thumb when retoning primary.

Disabled switches use --disabled for the track and --disabled-foreground for the thumb, at full opacity in both color modes. The thumb's position continues to show whether the value is on or off.

Kbd: content & composition ​

Kbd is a stateless typographic element, so the interesting axes are content (does a 1-char digit look balanced next to a 3-char word?) and composition (bare cap, grouped shortcut, embedded in another control).

Cap typography reads from --vueda-text-micro (11px) and the mono stack (--vueda-font-mono); the rounded chiclet shape follows --vueda-checkbox-radius so caps match the form-control language rather than the slab-control language. The keycap edge uses the hairline box-shadow contract, the same DPR-tracked edge as buttons and inputs.