Button

stable

Button is the default control for actions that affect the current interface, such as saving changes, opening a dialog, submitting a form, or deleting an item.

Use ButtonLink when the interaction navigates to another page or URL. Use IconButton when a familiar action must be represented by an icon without a visible text label.

Variants: primary secondary tertiary danger
Sizes: sm md lg
Icons
Full width
Loading
Disabled

Basic usage

Import Button from @ulubit/ui and provide a visible action label.

---
import { Button } from "@ulubit/ui";
---

<Button>Open settings</Button>

Variants

Choose a variant based on the action’s purpose and priority.

Variant Use for Example
primary The main action in a form, dialog, or section Save changes
secondary An alternative to the primary action Cancel
tertiary A lower-priority supporting action Clear filters
danger A destructive or difficult-to-reverse action Delete project

primary is the default. Avoid placing several primary buttons in the same action group when one action should have clear priority.

Use specific danger labels. Prefer Delete project over Delete, and consider confirmation when the result is irreversible or significant.

<div class="actions">
  <Button type="submit">Save changes</Button>
  <Button variant="secondary">Cancel</Button>
</div>

Sizes

Size controls the button’s dimensions, not its importance.

Size Use for
sm Compact interfaces such as table rows or toolbars
md Most page and form actions
lg Spacious layouts or intentionally larger targets

md is the default.

<Button size="sm">Edit</Button>
<Button>Continue</Button>
<Button size="lg">Create account</Button>

Icons

Most buttons do not need an icon. Add one only when it makes the action easier to recognize or reinforces an established meaning.

Use the start and end slots for decorative icons that support the visible label.

<Button>
  <PlusIcon slot="start" />
  Add member
</Button>

<Button variant="secondary">
  Continue
  <ArrowRightIcon slot="end" />
</Button>

The component hides slotted icons from assistive technologies. The visible label must still describe the action without relying on the icon.

Full width

Buttons are sized to their content by default. Use fullWidth when a button should fill the available inline space of its container, such as in a narrow form or stacked mobile action group.

<div class="form-actions">
  <Button type="submit" fullWidth>
    Save changes
  </Button>
</div>

The parent layout remains responsible for constraining the available width.

Button type

The component defaults to type="button" to avoid submitting a form accidentally.

Set type="submit" explicitly for form submission:

<form>
  <Button type="submit">Save changes</Button>
</form>

Use type="reset" only when resetting the entire form is intentional and clearly communicated.

Disabled state

Use the native disabled attribute when an action is unavailable.

<Button disabled>Save changes</Button>

A disabled button cannot be focused or activated. Avoid disabling an action without showing what the user must do to enable it.

Loading state

Use loading while an action is being processed.

<Button loading loadingLabel="Saving changes">
  Save changes
</Button>

While loading, the component:

  • shows a progress indicator while preserving the button’s dimensions
  • disables the native button to prevent repeated activation
  • sets aria-busy="true"
  • uses loadingLabel as the button’s accessible name

loadingLabel is not displayed as visible text. It must be non-empty and should describe the operation in progress, such as Saving changes or Deleting project.

The loading prop controls the state rendered by Astro. If loading starts or finishes after the page loads, the consuming application must update the button from client-side code because this Astro component does not rerender in the browser.

The consuming application remains responsible for starting and stopping the loading state, handling errors, and announcing important success or failure results.

Labels

Use a short label that describes the action.

Prefer:

  • Save changes
  • Add member
  • Delete account
  • Clear filters

Avoid vague labels such as:

  • OK
  • Yes
  • Click here
  • Submit

Use sentence case unless a proper name requires different capitalization.

Accessibility

Button renders a native <button> and retains its built-in keyboard, focus, and form behavior.

The component provides hover, active, focus-visible, disabled, and loading states across all variants.

Color tokens

Button variants use component-level color tokens from the UluBit color system. Each variant defines its background, interaction states, border, and text color independently.

The tokens below show the default values provided by @ulubit/foundations. Copy the relevant group to override a variant.

Primary

--color-button-primary-bg: var(--color-brand-600);
--color-button-primary-bg-hover: var(--color-brand-700);
--color-button-primary-bg-active: var(--color-brand-800);
--color-button-primary-border: transparent;
--color-button-primary-text: var(--color-neutral-50);

Secondary

--color-button-secondary-bg: transparent;
--color-button-secondary-bg-hover: color-mix(in oklch, var(--color-text) 8%, transparent);
--color-button-secondary-bg-active: color-mix(in oklch, var(--color-text) 14%, transparent);
--color-button-secondary-border: var(--color-border);
--color-button-secondary-text: var(--color-text);

Tertiary

--color-button-tertiary-bg: transparent;
--color-button-tertiary-bg-hover: color-mix(in oklch, var(--color-button-tertiary-text) 10%, transparent);
--color-button-tertiary-bg-active: color-mix(in oklch, var(--color-button-tertiary-text) 16%, transparent);
--color-button-tertiary-border: transparent;
--color-button-tertiary-text: light-dark(var(--color-brand-800), var(--color-brand-400));

Danger

--color-button-danger-bg: var(--color-red-700);
--color-button-danger-bg-hover: var(--color-red-800);
--color-button-danger-bg-active: var(--color-red-900);
--color-button-danger-border: transparent;
--color-button-danger-text: var(--color-neutral-50);

Disabled

Disabled colors are shared across variants where applicable. Primary and danger use the disabled background, secondary uses the disabled border, and all variants use the disabled text color. Tertiary keeps its background and border transparent when disabled.

--color-button-disabled-bg: light-dark(var(--color-neutral-200), var(--color-neutral-800));
--color-button-disabled-border: light-dark(var(--color-neutral-300), var(--color-neutral-700));
--color-button-disabled-text: light-dark(var(--color-neutral-500), var(--color-neutral-400));

Focus

The focus indicator uses the shared semantic focus color rather than a variant-specific Button token.

--color-focus: var(--color-neutral-500);

All Button variants use --color-focus for their :focus-visible outline.

Override the semantic token only when the project requires a different shared focus color. The resulting indicator must remain clearly visible against the colors immediately surrounding the control.

Overriding color tokens

Override only the tokens that need to change. Set them globally to change every button using that variant, or scope them to a class or parent element for a local customization.

For example, to customize a primary button:

.custom-button {
  --color-button-primary-bg: var(--color-brand-700);
  --color-button-primary-bg-hover: var(--color-brand-800);
  --color-button-primary-bg-active: var(--color-brand-800);
  --color-button-primary-text: var(--color-neutral-50);
}
<Button class="custom-button">
  Custom button
</Button>

Tokens that are not overridden continue to use their defaults.

API

The component accepts valid native <button> attributes except aria-busy, which is controlled by the loading state.

Property Type Default Description
variant "primary" | "secondary" | "tertiary" | "danger" "primary" Defines the action’s visual hierarchy and intent.
size "sm" | "md" | "lg" "md" Defines the button’s physical dimensions.
fullWidth boolean false Makes the button fill the available inline space of its container.
loading boolean false Displays the loading state and prevents repeated activation.
loadingLabel string Provides the accessible name while loading. Required when loading is true.
type "button" | "submit" | "reset" "button" Defines native button behavior.
disabled boolean false Prevents interaction with the button.
class string Adds a class alongside the component’s internal classes.
Default slot AstroComponentFactory Provides the visible label shown while idle.
start slot AstroComponentFactory Adds a decorative icon before the idle label.
end slot AstroComponentFactory Adds a decorative icon after the idle label.

Native attributes such as name, value, form, aria-describedby, and data-* are forwarded to the underlying <button> element.