Button
stableButton 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.
primary secondary tertiary dangersm md lgBasic 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
loadingLabelas 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.