Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

Components

Button

Full support Supported since v125. Full support Supported since v128. Full support Supported since v18.

Variants

Change the button variant with the variant prop.

Link
Link
Link
Link
---
import { Button } from "@opui/astro"
---
<div class="example-row">
<Button>Text</Button>
<Button disabled>Disabled</Button>
<Button href="#">Link</Button>
</div>
<div class="example-row">
<Button variant="outlined">Outlined</Button>
<Button variant="outlined" disabled>Disabled</Button>
<Button variant="outlined" href="#">Link</Button>
</div>
<div class="example-row">
<Button variant="tonal">Tonal</Button>
<Button variant="tonal" disabled>Disabled</Button>
<Button variant="tonal" href="#">Link</Button>
</div>
<div class="example-row">
<Button variant="filled">Filled</Button>
<Button variant="filled" disabled>Disabled</Button>
<Button variant="filled" href="#">Link</Button>
</div>
<div class="example-row">
<button class="ui-button">Text</button>
<button disabled class="ui-button ui-disabled">Disabled</button>
<a href="#" class="ui-button"> Link </a>
</div>
<div class="example-row">
<button class="ui-button ui-outlined">Outlined</button>
<button disabled class="ui-button ui-disabled ui-outlined">Disabled</button>
<a href="#" class="ui-button ui-outlined"> Link </a>
</div>
<div class="example-row">
<button class="ui-button ui-tonal">Tonal</button>
<button disabled class="ui-button ui-disabled ui-tonal">Disabled</button>
<a href="#" class="ui-button ui-tonal"> Link </a>
</div>
<div class="example-row">
<button class="ui-button ui-filled">Filled</button>
<button disabled class="ui-button ui-disabled ui-filled">Disabled</button>
<a href="#" class="ui-button ui-filled"> Link </a>
</div>

Colors

Pass color to apply a brand or destructive color: primary or critical. The default is a neutral gray.

---
import { Button } from "@opui/astro"
---
<div class="example-row">
<Button color="primary">Primary</Button>
<Button color="primary" variant="outlined">Outlined</Button>
<Button color="primary" variant="tonal">Tonal</Button>
<Button color="primary" variant="filled">Filled</Button>
</div>
<div class="example-row">
<Button color="critical">Critical</Button>
<Button color="critical" variant="outlined">Outlined</Button>
<Button color="critical" variant="tonal">Tonal</Button>
<Button color="critical" variant="filled">Filled</Button>
</div>
<div class="example-row">
<button class="ui-button ui-primary">Primary</button>
<button class="ui-button ui-outlined ui-primary">Outlined</button>
<button class="ui-button ui-tonal ui-primary">Tonal</button>
<button class="ui-button ui-filled ui-primary">Filled</button>
</div>
<div class="example-row">
<button class="ui-button ui-critical">Critical</button>
<button class="ui-button ui-outlined ui-critical">Outlined</button>
<button class="ui-button ui-tonal ui-critical">Tonal</button>
<button class="ui-button ui-filled ui-critical">Filled</button>
</div>

Buttons with icon and label

Include an icon alongside text by nesting it within the component.

---
import { Button } from "@opui/astro"
---
<Button>
Text
<svg> <!-- --> </svg>
</Button>
<Button variant="outlined">
Outlined
<svg> <!-- --> </svg>
</Button>
<Button variant="tonal">
Tonal
<svg> <!-- --> </svg>
</Button>
<Button variant="filled">
Filled
<svg> <!-- --> </svg>
</Button>
<Button>
<svg> <!-- --> </svg>
Text
</Button>
<Button variant="outlined">
<svg> <!-- --> </svg>
Outlined
</Button>
<Button variant="tonal">
<svg> <!-- --> </svg>
Tonal
</Button>
<Button variant="filled">
<svg> <!-- --> </svg>
Filled
</Button>
<div class="example-row">
<button class="ui-button">
Text
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button class="ui-button ui-outlined">
Outlined
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button class="ui-button ui-tonal">
Tonal
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button class="ui-button ui-filled">
Filled
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
</div>
<div class="example-row">
<button class="ui-button">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
Text
</button>
<button class="ui-button ui-outlined">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
Outlined
</button>
<button class="ui-button ui-tonal">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
Tonal
</button>
<button class="ui-button ui-filled">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
Filled
</button>
</div>

Keyboard

Use the <kbd> element to provide keyboard hints within a button.

---
import { Button } from "@opui/astro"
---
<Button>
Search <kbd>⌘K</kbd>
</Button>
<Button variant="outlined">
Save <kbd>⌘S</kbd>
</Button>
<Button variant="tonal">
Copy <kbd>⌘C</kbd>
</Button>
<Button variant="filled">
Delete <kbd>⌘⌫</kbd>
</Button>
<button class="ui-button">Search <kbd>⌘K</kbd></button>
<button class="ui-button ui-outlined">Save <kbd>⌘S</kbd></button>
<button class="ui-button ui-tonal">Copy <kbd>⌘C</kbd></button>
<button class="ui-button ui-filled">Delete <kbd>⌘⌫</kbd></button>

Icon-only

See Icon button documentation.

Sizes

Resize any button using the size prop.

---
import { Button } from "@opui/astro"
---
<Button size="small">Small</Button>
<Button>Default</Button>
<Button size="large">Large</Button>
<Button variant="filled" size="small">Small</Button>
<Button variant="filled">Default</Button>
<Button variant="filled" size="large">Large</Button>
<Button size="small" variant="outlined">
Small
<svg> <!-- --> </svg>
</Button>
<Button variant="outlined">
Default
<svg> <!-- --> </svg>
</Button>
<Button variant="outlined" size="large">
Large
<svg> <!-- --> </svg>
</Button>
<div class="example-row">
<button class="ui-button ui-small">Small</button>
<button class="ui-button">Default</button>
<button class="ui-button ui-large">Large</button>
</div>
<div class="example-row">
<button class="ui-button ui-small ui-filled">Small</button>
<button class="ui-button ui-filled">Default</button>
<button class="ui-button ui-large ui-filled">Large</button>
</div>
<div class="example-row">
<button class="ui-button ui-small ui-outlined">
Small
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button class="ui-button ui-outlined">
Default
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button class="ui-button ui-large ui-outlined">
Large
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
</div>

Disabled

Disable the button with the disabled prop.

---
import { Button } from "@opui/astro"
---
<Button disabled>Text</Button>
<div class="example-row">
<button disabled class="ui-button ui-disabled">Text</button>
<button disabled class="ui-button ui-disabled ui-outlined">Outlined</button>
<button disabled class="ui-button ui-disabled ui-tonal">Tonal</button>
<button disabled class="ui-button ui-disabled ui-filled">Filled</button>
</div>
<div class="example-row">
<button disabled class="ui-button ui-disabled">
Text
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button disabled class="ui-button ui-disabled ui-outlined">
Outlined
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button disabled class="ui-button ui-disabled ui-tonal">
Tonal
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
<button disabled class="ui-button ui-disabled ui-filled">
Filled
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.75 3a.75.75 0 0 1 .743.648l.007.102l.001 7.25h7.253a.75.75 0 0 1 .102 1.493l-.102.007h-7.253l.002 7.25a.75.75 0 0 1-1.493.101l-.007-.102l-.002-7.249H3.752a.75.75 0 0 1-.102-1.493L3.752 11h7.25L11 3.75a.75.75 0 0 1 .75-.75"
></path>
</svg>
</button>
</div>

File upload

Is it a button? Is it an input? You can find the docs for it here at least.

Anatomy

  1. Container
  2. Label text (optional)
  3. Icon (optional)

API

Prop Type Default Description
size "small", "large" - The size of the button.
variant "outlined", "tonal", "filled" - The visual variant of the button.
color "critical", "primary" - The color of the button. Default is a neutral gray.
href string - Renders as an tag if an href is provided.
disabled boolean - Button disabled state.

Browser support

Full support Supported since v125. Full support Supported since v128. Full support Supported since v18.

See also the full browser support guide.

Installation

@layer components.root {
:where(.ui-button) {
--_color: light-dark(var(--color-16), var(--color-1));
--_color-contrast: light-dark(var(--color-1), var(--color-16));
--_color-tonal: var(--surface-tonal);
--_accent: var(--_color);
--_accent-contrast: var(--_color-contrast);
--_accent-tonal: var(--_color-tonal);
--_accent-tonal-contrast: var(--_color);
--_default-hover-bg-color: light-dark(
oklch(from var(--_accent) l 0.01 h / 10%),
oklch(from var(--_accent) l 0.01 h / 20%)
);
--_default-active-bg-color: light-dark(
oklch(from var(--_accent) l 0.06 h / 20%),
oklch(from var(--_accent) l 0.06 h / 30%)
);
--_filled-hover-bg-color: light-dark(
oklch(from var(--_accent) calc(l + 0.2) c h),
oklch(from var(--_accent) calc(l - 0.1) c h)
);
--_filled-active-bg-color: light-dark(
oklch(from var(--_accent) calc(l + 0.3) c h),
oklch(from var(--_accent) calc(l - 0.15) c h)
);
--_outlined-active-bg-color: var(--_filled-active-bg-color);
--_bg-color: transparent;
--_border-color: transparent;
--_border-width: var(--border-size-1);
--_font-size: var(--font-size-1);
--_min-height: var(--button-size);
--_border-radius: var(--button-border-radius, var(--radius-2));
--_text-color: var(--_accent);
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
align-items: center;
background-color: var(--_bg-color);
border: var(--_border-width) solid var(--_border-color);
border-radius: var(--_border-radius);
color: var(--_text-color);
display: inline-flex;
font-size: var(--_font-size);
font-weight: 700;
gap: var(--size-2);
justify-content: center;
min-block-size: var(--_min-height);
padding-block: 0.5ex;
padding-inline: 1.5ex;
text-align: center;
text-decoration: none;
transition:
background-color 0.1s ease,
color 0.1s ease,
border-color 0.1s ease,
box-shadow 0.1s ease;
user-select: none;
/* Colors — shared interaction model for critical and primary.
Each color modifier below only swaps --_color; the tonal surface,
contrast text, and hover/active strategy are identical.
The .critical scope class in theme.css redirects --palette-source so
--color-6 below becomes a tint of the active color. */
&.ui-critical,
&.ui-primary {
--_color-contrast: var(--gray-1);
--_color-tonal: var(--color-6);
--_accent-tonal-contrast: var(--text-primary-contrast);
--_default-hover-bg-color: oklch(from var(--_accent) l c h / 15%);
--_default-active-bg-color: oklch(from var(--_accent) l c h / 25%);
--_filled-hover-bg-color: oklch(from var(--_accent) calc(l - 0.1) c h);
--_filled-active-bg-color: oklch(from var(--_accent) calc(l - 0.15) c h);
--_outlined-active-bg-color: oklch(from var(--_accent) calc(l - 0.1) c h);
}
&.ui-critical {
--_color: var(--critical);
}
&.ui-primary {
--_color: var(--primary);
}
/* Disabled */
&:where([disabled]) {
--_text-color: color-mix(
in oklch,
var(--text-muted) 50%,
var(--surface-default)
);
cursor: not-allowed;
opacity: 0.64;
}
/* Hover & Active */
&:where(:not([disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--_default-hover-bg-color);
}
&:where(:hover:active),
&[aria-current="page"] {
--_bg-color: var(--_default-active-bg-color);
}
}
/* Icons / Keyboard */
&:where(:has(svg), &.ui-icon-only) {
gap: 1ex;
svg {
color: currentColor;
max-block-size: 0.7lh;
}
}
/* Keyboard input */
:is(kbd) {
background-color: oklch(from currentColor l c h / 10%);
border: 0;
color: oklch(from currentColor l c h / 80%);
font-size: 0.8em;
font-weight: 400;
line-height: 1.2;
}
/* Sizes — heights aliased from the shared --control-size scale.
Override --button-size-* in a parent scope to resize an entire region. */
&.ui-x-small {
--_min-height: var(--button-size-x-small);
--_font-size: var(--font-size-0);
padding-block: 0;
padding-inline: 0.5ex;
}
&.ui-small {
--_min-height: var(--button-size-small);
--_font-size: var(--font-size-05);
padding-block: 0;
padding-inline: 0.75ex;
}
&.ui-large {
--_min-height: var(--button-size-large);
padding-inline: 4ex;
}
/* Variants */
&.ui-outlined {
--_bg-color: transparent;
--_border-color: var(--_accent);
--_text-color: var(--_accent);
&:where(:not([disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--_accent);
--_border-color: var(--_accent);
--_text-color: var(--_accent-contrast);
}
&:where(:active) {
--_bg-color: var(--_outlined-active-bg-color);
--_border-color: var(--_outlined-active-bg-color);
--_text-color: var(--_accent-contrast);
}
}
}
&.ui-tonal {
--_bg-color: var(--_accent-tonal);
--_text-color: var(--_accent-tonal-contrast);
&:where(:not([disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--_accent);
--_text-color: var(--_accent-contrast);
}
&:where(:active) {
--_bg-color: var(--_outlined-active-bg-color);
--_text-color: var(--_accent-contrast);
}
}
}
&.ui-filled {
--_bg-color: var(--_accent);
--_text-color: var(--_accent-contrast);
&:where(:not([disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--_filled-hover-bg-color);
}
&:where(:active) {
--_bg-color: var(--_filled-active-bg-color);
}
}
}
}
/* file input */
:where(input[type="file"]) {
align-self: flex-start;
border: var(--border-size-1) solid var(--surface-filled);
border-radius: var(--radius-2);
box-shadow: var(--inner-shadow-4);
color: var(--text-muted-contrast);
cursor: initial;
max-inline-size: 100%;
padding: 0;
}
:where(input[type="file"])::-webkit-file-upload-button,
:where(input[type="file"])::file-selector-button {
cursor: pointer;
margin-inline-end: var(--size-relative-6);
}
}