<button class="button">Text</button>
<button class="button outlined">Outlined</button>
<button class="button tonal">Tonal</button>
<button class="button filled">Filled</button>
<button class="button elevated">Elevated</button>
Button
Variants
Text
<button class="button">Text</button>
<button class="button" disabled>Disabled</button>
<a href="#" class="button">Link</a>
Outlined
<button class="button outlined">Outlined</button>
<button class="button outlined" disabled>Disabled</button>
<a href="#" class="button outlined">Link</a>
Tonal
<button class="button tonal">Tonal</button>
<button class="button tonal" disabled>Disabled</button>
<a href="#" class="button tonal">Link</a>
Filled
<button class="button filled">Filled</button>
<button class="button filled" disabled>Disabled</button>
<a href="#" class="button filled">Link</a>
Elevated
<button class="button elevated">Elevated</button>
<button class="button elevated" disabled>Disabled</button>
<a href="#" class="button elevated">Link</a>
Buttons with icon and label
<!-- Label + icon -->
<button class="button">
Label
<svg></svg>
</button>
<!-- Icon + label -->
<button class="button">
<svg></svg>
Label
</button>
Icon button
The .sr-only
(screen reader only) class removes the visible text but still allows screen readers to access it.
<button class="button">
<span class="sr-only">Label</span>
<svg></svg>
</button>
Without label
If you can't provide a label or want to control the button from its root you can use the .icon-only
class.
<button class="button icon-only">
<svg></svg>
</button>
File upload
Sizes
Resize any button with the .small
and .large
modifiers.
<button class="button small">Small</button>
<button class="button">Medium</button>
<button class="button large">Large</button>
Disabled
Add disabled styling with the disabled
attribute or the .disabled
class.
<button class="button" disabled>Label</button>
<button class="button" disabled>
<span class="sr-only">Label</span>
<svg></svg>
</button>
Ripple effect
The ripple effect on button press is enabled by default. Here's how you disable it.
Either disable it by setting the ripple size to 0 in your theme config:
--button-ripple-size: 0;
... or to your button-variants.css
file and remove all the ripple related styles:
:where(button, .button) {
/* ... */
/* Ripple effect */
background-position: center;
transition: background 0.8s;
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
--ripple: radial-gradient(circle, transparent 1%, var(--_bg-color) 1%) center/15000%;
}
&:where(:hover:active) {
background-size: 100%;
transition: background 0s;
}
}
}
Colors
These are the out-of-the-box colors generated by the Open Props UI theme.css
file. You are free to add as many and as few as you want to your styles.
Anatomy
- Container
- Label text (optional)
- Icon (optional)
API
These are the classes and attributes a button can be styled with. As usual, feel free to add your own!
Type | Modifiers | Default | Description |
---|---|---|---|
Disabled | [disabled] , .disabled | - | If applied, the button is disabled. |
Icon-only | .icon-only | - | If applied, the button won't show its inner label. |
Sizes | .small , .medium , .large | .medium | The size of the button. |
Variants | .text , .outlined , .tonal , .filled , .elevated | .text | The variant to use. |
Browser compatibility
Installation
:where(.button, input:is([type="button"], [type="submit"], [type="reset"])),
:where(input[type="file"])::-webkit-file-upload-button,
:where(input[type="file"])::file-selector-button {
--_bg-color: initial;
--_border-color: initial;
--_border-radius: var(--button-border-radius, initial);
--_font-size: initial;
--_text-color: initial;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
align-items: center;
background: var(--_bg-color) var(--ripple, none);
border-radius: var(--_border-radius);
border: var(--border-size-1) solid var(--_border-color);
color: var(--_text-color);
display: inline-flex;
font-size: var(--_font-size);
font-weight: 700;
gap: var(--size-2);
justify-content: center;
padding-block: 0.5ex;
padding-inline: 1.5ex;
text-align: center;
text-decoration: none;
transition:
background-color 0.2s var(--ease-out-3),
box-shadow 0.2s var(--ease-out-3),
border-color 0.2s var(--ease-out-3),
color 0.2s var(--ease-out-3);
user-select: none;
}
/* file input */
:where(input[type="file"]) {
align-self: flex-start;
border-radius: var(--radius-2);
border: var(--border-size-1) solid var(--surface-filled);
box-shadow: var(--inner-shadow-4);
color: var(--text-color-2-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);
}
:where(.button) {
--_bg-color: transparent;
--_border-color: transparent;
--_text-color: var(--primary);
/* TODO */
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
--_bg-color: light-dark(
color-mix(in oklch, white 90%, black),
color-mix(in oklch, white 40%, black)
);
}
&:where(:hover:active) {
--_bg-color: light-dark(
color-mix(in oklch, white 95%, black),
color-mix(in oklch, white 45%, black)
);
}
}
&:where(.disabled, [disabled]) {
opacity: 0.64;
--_text-color: color-mix(
in oklch,
var(--text-color-2) 50%,
var(--surface-default)
);
cursor: not-allowed;
}
/* Sizes */
&.small {
padding-block: 0;
padding-inline: 1ex;
}
&.medium {
padding-block: 0.5ex;
padding-inline: 1.5ex;
}
&.large {
padding-block: 1ex;
padding-inline: 2.5ex;
}
&.text {
--_bg-color: transparent;
--_border-color: transparent;
--_text-color: initial;
}
/* Variants */
&.outlined {
--_bg-color: var(--surface-default);
--_border-color: var(--color-8);
--_text-color: var(--color-8);
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--color-10);
--_border-color: var(--color-10);
--_text-color: var(--color-1);
}
&:where(:active) {
--_bg-color: var(--color-9);
--_border-color: var(--color-9);
--_text-color: var(--color-1);
}
}
&:where(.disabled, [disabled]) {
--_bg-color: var(--surface-default);
--_border-color: color-mix(
in oklch,
var(--text-color-2) 20%,
var(--surface-default)
);
--_text-color: color-mix(
in oklch,
var(--text-color-2) 40%,
var(--surface-default)
);
}
}
&.tonal {
--_bg-color: var(--color-5);
--_text-color: var(--color-16);
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--color-9);
--_border-color: var(--color-9);
}
&:where(:active) {
--_bg-color: var(--color-7);
--_border-color: var(--color-7);
}
}
&:where(.disabled, [disabled]) {
--_bg-color: color-mix(
in oklch,
var(--text-color-2) 8%,
var(--surface-default)
);
--_text-color: color-mix(
in oklch,
var(--text-color-2) 70%,
var(--surface-default)
);
}
}
&.filled {
--_bg-color: var(--color-8);
--_text-color: var(--color-1);
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
--_bg-color: var(--color-10);
--_border-color: var(--color-10);
}
&:where(:active) {
--_bg-color: var(--color-9);
--_border-color: var(--color-9);
}
}
&:where(.disabled, [disabled]) {
--_bg-color: color-mix(
in oklch,
var(--text-color-2) 20%,
var(--surface-default)
);
--_text-color: color-mix(
in oklch,
var(--text-color-2) 70%,
var(--surface-default)
);
}
}
&.elevated {
--_bg-color: light-dark(var(--gray-1), var(--gray-12));
--_text-color: var(--color-8);
box-shadow: var(--shadow-3);
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
box-shadow: var(--shadow-4);
}
&:where(:active) {
box-shadow: var(--shadow-6);
}
}
@container style(--color-scheme: dark) {
box-shadow: var(--shadow-4);
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
box-shadow: var(--shadow-5);
}
&:where(:active) {
box-shadow: var(--shadow-6);
}
}
}
&:where(.disabled, [disabled]) {
--_bg-color: color-mix(
in oklch,
var(--text-color-2) 8%,
var(--surface-elevated)
);
--_text-color: color-mix(
in oklch,
var(--text-color-2) 70%,
var(--surface-elevated)
);
}
}
/* Ripple effect */
background-position: center;
transition: background 0.8s;
&:where(:not(.disabled, [disabled])) {
&:where(:not(:active):hover) {
--ripple: radial-gradient(circle, transparent 1%, var(--_bg-color) 1%)
center/15000%;
}
&:where(:hover:active) {
background-size: var(--button-ripple-size);
transition: background 0s;
}
}
}
/*
Inspired by ModernCSS.dev
https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#creating-variant-styles-with-has
The `.sr-only` class refers to the "screen reader only" util which
removes the visible text but still allows screen readers to
access it.
*/
:where(.button) {
/* Base */
&:where(:has(svg), &.icon-only) {
gap: 1ex;
& svg {
fill: currentColor;
}
}
/* Icon-only */
&:where(:has(.sr-only), &.icon-only) {
--_text-color: inherit;
aspect-ratio: 1;
border: 0;
border-radius: var(--button-border-radius);
gap: 0;
padding: 0.5ex;
svg {
inline-size: 85%;
pointer-events: none;
}
&.small {
padding: 0.125ex;
& * {
scale: 0.9;
transform-origin: center;
}
}
&.large {
padding: 1ex;
& * {
scale: 1.1;
transform-origin: center;
}
}
}
}