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.
<script setup lang="ts">import { Button } from "opui-css/vue"</script>
<template> <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></template><!--[--><div class="example-row"> <button class="ui-button"><!--[-->Text<!--]--></button ><button class="ui-button ui-disabled" disabled> <!--[-->Disabled<!--]--></button ><a class="ui-button" href="#"><!--[-->Link<!--]--></a></div><div class="example-row"> <button class="ui-button ui-outlined"><!--[-->Outlined<!--]--></button ><button class="ui-button ui-disabled ui-outlined" disabled> <!--[-->Disabled<!--]--></button ><a class="ui-button ui-outlined" href="#"><!--[-->Link<!--]--></a></div><div class="example-row"> <button class="ui-button ui-tonal"><!--[-->Tonal<!--]--></button ><button class="ui-button ui-disabled ui-tonal" disabled> <!--[-->Disabled<!--]--></button ><a class="ui-button ui-tonal" href="#"><!--[-->Link<!--]--></a></div><div class="example-row"> <button class="ui-button ui-filled"><!--[-->Filled<!--]--></button ><button class="ui-button ui-disabled ui-filled" disabled> <!--[-->Disabled<!--]--></button ><a class="ui-button ui-filled" href="#"><!--[-->Link<!--]--></a></div><!--]-->Colors
Pass color to apply a brand or destructive color: primary or critical. The default is a neutral gray.
<script setup lang="ts">import { Button } from "opui-css/vue"</script>
<template> <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></template><!--[--><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.
<script setup lang="ts">import { Button } from "opui-css/vue"</script>
<template> <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></template><!--[--><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.
<script setup lang="ts">import { Button } from "opui-css/vue"</script>
<template> <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></template><!--[--><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.
<script setup lang="ts">import { Button } from "opui-css/vue"</script>
<template> <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></template><!--[--><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.
<script setup lang="ts">import { Button } from "opui-css/vue"</script>
<template> <Button disabled>Text</Button></template><!--[--><div class="example-row"> <button class="ui-button ui-disabled" disabled><!--[-->Text<!--]--></button ><button class="ui-button ui-disabled ui-outlined" disabled> <!--[-->Outlined<!--]--></button ><button class="ui-button ui-disabled ui-tonal" disabled> <!--[-->Tonal<!--]--></button ><button class="ui-button ui-disabled ui-filled" disabled> <!--[-->Filled<!--]--> </button></div><div class="example-row"> <button class="ui-button ui-disabled" 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 class="ui-button ui-disabled ui-outlined" disabled> <!--[--> 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-disabled ui-tonal" disabled> <!--[--> 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-disabled ui-filled" disabled> <!--[--> 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 <a> 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) { --_motion: var(--motion, 1); --_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 calc(0.1s * var(--_motion)) ease, color calc(0.1s * var(--_motion)) ease, border-color calc(0.1s * var(--_motion)) ease, box-shadow calc(0.1s * var(--_motion)) 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); }}