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.
---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
- Container
- Label text (optional)
- 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); }}