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

Buttons allow users to take actions, and make choices, with a single tap.

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="button">Text</button
><button disabled class="button disabled">Disabled</button
><a href="#" class="button">Link</a>
</div>
<div class="example-row">
<button class="button outlined">Outlined</button
><button disabled class="button disabled outlined">Disabled</button
><a href="#" class="button outlined">Link</a>
</div>
<div class="example-row">
<button class="button tonal">Tonal</button
><button disabled class="button disabled tonal">Disabled</button
><a href="#" class="button tonal">Link</a>
</div>
<div class="example-row">
<button class="button filled">Filled</button
><button disabled class="button disabled filled">Disabled</button
><a href="#" class="button 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="button primary">Primary</button
><button class="button outlined primary">Outlined</button
><button class="button tonal primary">Tonal</button
><button class="button filled primary">Filled</button>
</div>
<div class="example-row">
<button class="button critical">Critical</button
><button class="button outlined critical">Outlined</button
><button class="button tonal critical">Tonal</button
><button class="button filled 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="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="button 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="button 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="button 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="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="button 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="button 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="button 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="button">Search <kbd>⌘K</kbd></button
><button class="button outlined">Save <kbd>⌘S</kbd></button
><button class="button tonal">Copy <kbd>⌘C</kbd></button
><button class="button 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="button small">Small</button
><button class="button">Default</button
><button class="button large">Large</button>
</div>
<div class="example-row">
<button class="button small filled">Small</button
><button class="button filled">Default</button
><button class="button large filled">Large</button>
</div>
<div class="example-row">
<button class="button small 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="button 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="button large 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="button disabled">Text</button
><button disabled class="button disabled outlined">Outlined</button
><button disabled class="button disabled tonal">Tonal</button
><button disabled class="button disabled filled">Filled</button>
</div>
<div class="example-row">
<button disabled class="button 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="button disabled 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="button disabled 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="button disabled 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(.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 .1s ease, color .1s ease,
border-color .1s ease, box-shadow .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. */
&.critical,
&.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);
}
&.critical {
--_color: var(--critical);
}
&.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), &.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. */
&.x-small {
--_min-height: var(--button-size-x-small);
--_font-size: var(--font-size-0);
padding-block: 0;
padding-inline: .5ex;
}
&.small {
--_min-height: var(--button-size-small);
--_font-size: var(--font-size-05);
padding-block: 0;
padding-inline: .75ex;
}
&.large {
--_min-height: var(--button-size-large);
padding-inline: 4ex;
}
/* Variants */
&.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);
}
}
}
&.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);
}
}
}
&.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);
}
}