Button
⟦Áçtíóñ búttóñ wíth váríáñtš áñd šížéš, íñçlúdíñg íçóñ-óñlý.~~~~~~~~~~~~~~~~~~~~~⟧
- ⟦Brówšérš~~~⟧
- Chrome 123+Firefox 121+Safari 17.5+
- ⟦Tíér~~⟧
- 0
- ⟦Búñdlé çómpréššéd~~~~~~⟧
- 340 B
- ⟦Fórçéd láýóút búdgét~~~~~~~⟧
- 0 ms
- ⟦Áxé búdgét~~~~⟧
- 0
- ⟦ŠŠR çómpátíblé~~~~~⟧
- ⟦Ýéš~~⟧
- ⟦Públíšhéd çóñtráçt~~~~~~~⟧
- ⟦Téšt hárñéšš~~~~~⟧
⟦Pláýgróúñd~~~~⟧
Default
Secondary
Outline
Ghost
Destructive
Link
Loading
import { Button } from "@gunkin/uiify";
export default function Example() {
return <Button className="">Save</Button>;
}/* Button — pill-shaped and monochrome: a filled foreground action by default, secondary for
the tonal one, outline for the bordered one, ghost for a surface that only appears on
hover, link for inline text actions.
Filled variants are lit from above by two layers that sit between the fill and the label:
- `::before`, the wash: a blurred copy of the shape raised by half its height, so only its
soft lower edge glows inside the upper half. `filter` runs before `clip-path`, which lets
one box blur itself and still be cut back to the button outline.
- `::after`, the rim: a half-pixel ring whose colour fades out towards the bottom. A
box-shadow cannot take a gradient and `border-image` ignores the radius, so the ring is
a gradient box with its content box masked out. */
@layer components {
:where([data-uiify-button]) {
--uiify-button-rim: rgb(255 255 255 / 20%);
--uiify-button-wash: light-dark(rgb(255 255 255 / 15%), rgb(255 255 255 / 30%));
position: relative;
isolation: isolate;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
min-block-size: 44px;
/* Never narrower than tall: an icon size only has to drop the padding to be a circle. */
min-inline-size: 44px;
padding-block: 0;
padding-inline: 1.25rem;
border: var(--border-width) solid transparent;
border-radius: var(--radius-full);
background-color: var(--foreground);
background-clip: padding-box;
color: var(--background);
font-size: var(--text-base);
font-weight: var(--weight-medium);
line-height: 1.5rem;
text-decoration: none;
cursor: pointer;
transition: var(--transition-colors);
}
:where([data-uiify-button])::before,
:where([data-uiify-button])::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
pointer-events: none;
}
:where([data-uiify-button])::before {
background-color: var(--uiify-button-wash);
filter: blur(10px);
translate: 0 -50%;
clip-path: inset(50% 0 -50% 0 round var(--radius-full));
}
:where([data-uiify-button])::after {
padding: 0.5px;
background-image: linear-gradient(to bottom, var(--uiify-button-rim), transparent 75%);
mask:
linear-gradient(#fff 0 0) content-box exclude,
linear-gradient(#fff 0 0);
}
/* Sizes: md is the default metrics above. */
[data-uiify-button]:is([data-size="sm"], [data-size="icon-sm"]) {
min-block-size: 32px;
min-inline-size: 32px;
padding-inline: 0.75rem;
gap: 0.25rem;
font-size: var(--text-sm);
line-height: 1.25rem;
}
[data-uiify-button]:is([data-size="lg"], [data-size="icon-lg"]) {
min-block-size: 56px;
min-inline-size: 56px;
padding-inline: 1.75rem;
font-size: var(--text-lg);
line-height: 1.75rem;
}
/* Icon sizes: the same three heights with no padding, for a single icon. */
[data-uiify-button][data-size^="icon"] {
padding-inline: 0;
}
@media (any-hover: hover) and (any-pointer: fine) {
[data-uiify-button]:hover:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 90%, var(--background));
}
}
[data-uiify-button]:active:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 80%, var(--background));
}
[data-uiify-button]:disabled,
[data-uiify-button][data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
/* Secondary: tonal low-emphasis fill. */
[data-uiify-button][data-variant="secondary"] {
--uiify-button-rim: light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 10%));
--uiify-button-wash: light-dark(rgb(255 255 255 / 20%), rgb(255 255 255 / 5%));
background-color: color-mix(in oklch, var(--foreground) 10%, transparent);
color: var(--foreground);
}
/* Outline: no fill and no wash, a hairline border in the text colour. Its rim is a full
border-width ring laid over that border, so the top edge reads slightly stronger and
fades into the plain border below. */
[data-uiify-button][data-variant="outline"] {
--uiify-button-rim: color-mix(in oklch, var(--foreground) 25%, transparent);
border-color: color-mix(in oklch, var(--foreground) 20%, transparent);
background-color: transparent;
color: var(--foreground);
}
[data-uiify-button][data-variant="outline"]::before {
content: none;
}
[data-uiify-button][data-variant="outline"]::after {
inset: calc(-1 * var(--border-width));
padding: var(--border-width);
}
[data-uiify-button][data-variant="outline"]:active:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 16%, transparent);
}
/* Ghost: text only; the tonal surface appears on hover. Flat, so no light layers. */
[data-uiify-button][data-variant="ghost"] {
background-color: transparent;
color: var(--foreground);
}
[data-uiify-button][data-variant="ghost"]::before,
[data-uiify-button][data-variant="ghost"]::after {
content: none;
}
[data-uiify-button][data-variant="ghost"]:active:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 16%, transparent);
}
[data-uiify-button][data-variant="secondary"]:active:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 26%, transparent);
}
/* Destructive: irreversible or dangerous actions. */
[data-uiify-button][data-variant="destructive"] {
--uiify-button-wash: rgb(255 255 255 / 15%);
background-color: var(--destructive);
color: var(--destructive-foreground);
}
[data-uiify-button][data-variant="destructive"]:active:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--destructive), black 20%);
}
@media (any-hover: hover) and (any-pointer: fine) {
[data-uiify-button][data-variant="outline"]:hover:not(:disabled, [data-disabled]),
[data-uiify-button][data-variant="ghost"]:hover:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 10%, transparent);
}
[data-uiify-button][data-variant="secondary"]:hover:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--foreground) 20%, transparent);
}
[data-uiify-button][data-variant="destructive"]:hover:not(:disabled, [data-disabled]) {
background-color: color-mix(in oklch, var(--destructive), black 12%);
}
}
}
@layer components {
/* Selected: the chosen item of a set or a toggle that is on, filled with the brand colour.
The state is read from whatever already carries it: the `selected` prop, a pressed toggle,
the current page, or — for a label styled as a button — the checked native control right
before it, so a toggle works with no JavaScript. */
[data-uiify-button]:is([data-selected], [aria-pressed="true"], [aria-current="page"]),
:checked + [data-uiify-button] {
--uiify-button-rim: rgb(255 255 255 / 20%);
--uiify-button-wash: rgb(255 255 255 / 15%);
border-color: transparent;
background-color: var(--primary);
color: var(--primary-foreground);
}
@media (any-hover: hover) and (any-pointer: fine) {
[data-uiify-button]:is([data-selected], [aria-pressed="true"], [aria-current="page"]):hover:not(
:disabled,
[data-disabled]
),
:checked:not(:disabled) + [data-uiify-button]:hover {
background-color: color-mix(in oklch, var(--primary) 88%, var(--primary-foreground));
}
}
/* A label styled as a button follows the native control it belongs to. */
:disabled + [data-uiify-button] {
opacity: 0.5;
cursor: not-allowed;
}
[data-uiify-button][hidden] {
display: none;
}
}
@layer components {
/* Link: an inline text action with no surface, on a button or an anchor alike. */
[data-uiify-button][data-variant="link"],
[data-uiify-button][data-variant="link"]:active {
padding-inline: 0;
border: 0;
border-radius: 0;
background: transparent;
color: var(--primary);
text-decoration: underline;
text-underline-offset: 0.2em;
}
[data-uiify-button][data-variant="link"] {
min-inline-size: 0;
}
@media (any-hover: hover) and (any-pointer: fine) {
[data-uiify-button][data-variant="link"]:hover {
padding-inline: 0;
border: 0;
border-radius: 0;
background: transparent;
color: var(--primary);
text-decoration: underline;
text-underline-offset: 0.2em;
}
}
[data-uiify-button][data-variant="link"]::before,
[data-uiify-button][data-variant="link"]::after {
content: none;
}
[data-uiify-button][data-variant="link"]:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 3px;
}
}import { Button } from "@gunkin/uiify";
export default function Example() {
return (
<Button
className={
"focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ri" +
"ng)_16%,_transparent)] [--uiify-button-rim:rgb(255_255_255_/_20%)] [--uiify-button-wash:light-dark(rgb(255_" +
"255_255_/_15%),_rgb(255_255_255_/_30%))] relative isolate inline-flex items-center justify-center [gap:0.37" +
"5rem] [min-block-size:44px] [min-inline-size:44px] py-0 [padding-inline:1.25rem] border-[length:var(--borde" +
"r-width)] border-solid border-transparent rounded-[var(--radius-full)] bg-foreground bg-clip-padding text-b" +
"ackground text-base font-[number:var(--weight-medium)] leading-6 no-underline cursor-pointer [transition:va" +
"r(--transition-colors)] before:content-[''] before:absolute before:inset-0 before:z-[-1] before:[border-rad" +
"ius:inherit] before:pointer-events-none after:content-[''] after:absolute after:inset-0 after:z-[-1] after:" +
"[border-radius:inherit] after:pointer-events-none before:[background-color:var(--uiify-button-wash)] before" +
":[filter:blur(10px)] before:[translate:0_-50%] before:[clip-path:inset(50%_0_-50%_0_round_var(--radius-full" +
"))] after:[padding:0.5px] after:[background-image:linear-gradient(to_bottom,_var(--uiify-button-rim),_trans" +
"parent_75%)] after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [&:is([d" +
"ata-size=sm],[data-size=icon-sm])]:[min-block-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[min-in" +
"line-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[padding-inline:0.75rem] [&:is([data-size=sm],[d" +
"ata-size=icon-sm])]:[gap:0.25rem] [&:is([data-size=sm],[data-size=icon-sm])]:text-sm [&:is([data-size=sm],[" +
"data-size=icon-sm])]:leading-5 [&:is([data-size=lg],[data-size=icon-lg])]:[min-block-size:56px] [&:is([data" +
"-size=lg],[data-size=icon-lg])]:[min-inline-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[padding-" +
"inline:1.75rem] [&:is([data-size=lg],[data-size=icon-lg])]:text-lg [&:is([data-size=lg],[data-size=icon-lg]" +
")]:leading-7 [&[data-size^=icon]]:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:hover:[&:not(:di" +
"sabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_90%,_var(--background))] a" +
"ctive:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_80%,_var(-" +
"-background))] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:c" +
"ursor-not-allowed data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[ar" +
"ia-current=page]))]:[--uiify-button-rim:light-dark(rgb(0_0_0_/_5%),_rgb(255_255_255_/_10%))] data-[variant=" +
"secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-but" +
"ton-wash:light-dark(rgb(255_255_255_/_20%),_rgb(255_255_255_/_5%))] data-[variant=secondary]:[&:where(:not(" +
":checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[background-color:color-mix(in_oklch," +
"_var(--foreground)_10%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[ar" +
"ia-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:[&:where(:not(:checked+*,[da" +
"ta-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreg" +
"round)_25%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=tru" +
"e],[aria-current=page]))]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] data-[vari" +
"ant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transpa" +
"rent data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page" +
"]))]:text-foreground data-[variant=outline]:before:content-none data-[variant=outline]:after:[inset:calc(-1" +
"_*_var(--border-width))] data-[variant=outline]:after:[padding:var(--border-width)] data-[variant=outline]:" +
"[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled" +
",[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant" +
"=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent " +
"data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:te" +
"xt-foreground data-[variant=ghost]:before:content-none data-[variant=ghost]:after:content-none data-[varian" +
"t=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(" +
":disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data" +
"-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:ac" +
"tive:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_26%,_transp" +
"arent)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-curre" +
"nt=page]))]:[--uiify-button-wash:rgb(255_255_255_/_15%)] data-[variant=destructive]:[&:where(:not(:checked+" +
"*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-destructive data-[variant=destructive]:text" +
"-destructive-foreground data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=t" +
"rue],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch," +
"_var(--destructive),_black_20%)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=outline]" +
":[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled" +
",[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-" +
"hover:_hover)_and_(any-pointer:_fine)]:data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-" +
"pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(i" +
"n_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[var" +
"iant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[" +
"&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)" +
"] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=destructive]:[&:where(:not(:checked+*,[" +
"data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[backgro" +
"und-color:color-mix(in_oklch,_var(--destructive),_black_12%)] [&:is([data-selected],[aria-pressed=true],[ar" +
"ia-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[aria-pressed=true],[a" +
"ria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [&:is([data-selected],[aria-pressed=true]," +
"[aria-current=page])]:border-transparent [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:bg" +
"-primary [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-primary-foreground [:checked+" +
"&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [:checked+&]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [:" +
"checked+&]:border-transparent [:checked+&]:bg-primary [:checked+&]:text-primary-foreground [@media_(any-hov" +
"er:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true],[aria-current=page]):hover:no" +
"t(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreg" +
"round))] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:not(:disabled)+&:hover]:[background-" +
"color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [:disabled+&]:opacity-50 [:disabl" +
"ed+&]:cursor-not-allowed [&[hidden]]:hidden data-[variant=link]:px-0 data-[variant=link]:border-0 data-[var" +
"iant=link]:border-current data-[variant=link]:border-none data-[variant=link]:rounded-none data-[variant=li" +
"nk]:bg-transparent data-[variant=link]:text-primary data-[variant=link]:underline data-[variant=link]:[text" +
"-underline-offset:0.2em] data-[variant=link]:active:px-0 data-[variant=link]:active:border-0 data-[variant=" +
"link]:active:border-current data-[variant=link]:active:border-none data-[variant=link]:active:rounded-none " +
"data-[variant=link]:active:bg-transparent data-[variant=link]:active:text-primary data-[variant=link]:activ" +
"e:underline data-[variant=link]:active:[text-underline-offset:0.2em] data-[variant=link]:[min-inline-size:0" +
"] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:px-0 [@media_(any-hover:_ho" +
"ver)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-0 [@media_(any-hover:_hover)_and_(any-pointe" +
"r:_fine)]:data-[variant=link]:hover:border-current [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data" +
"-[variant=link]:hover:border-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:h" +
"over:rounded-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:bg-transpar" +
"ent [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:text-primary [@media_(any" +
"-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:underline [@media_(any-hover:_hover)_and_" +
"(any-pointer:_fine)]:data-[variant=link]:hover:[text-underline-offset:0.2em] data-[variant=link]:before:con" +
"tent-none data-[variant=link]:after:content-none data-[variant=link]:focus-visible:[outline:2px_solid_var(-" +
"-ring)] data-[variant=link]:focus-visible:[outline-offset:3px] [[data-uiify-toggle]>input:focus-visible+&]:" +
"[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-toggle-" +
"group]_[data-part=item]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(" +
"--ring)_16%,_transparent)] [[data-uiify-modal][data-part=close]&]:border-transparent [[data-uiify-modal][da" +
"ta-part=close]&]:[background-color:color-mix(in_oklch,_var(--foreground)_8%,_transparent)] [[data-uiify-mod" +
"al][data-part=close]&]:text-foreground [[data-uiify-modal][data-part=close]&:hover]:[background-color:color" +
"-mix(in_oklch,_var(--foreground)_14%,_transparent)] [[data-uiify-modal][data-part=close]&:active]:[backgrou" +
"nd-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [[data-uiify-pagination]_span&:not([data-" +
"disabled])]:cursor-default [[data-uiify-input-group]>[data-part=addon]>&]:[min-inline-size:0] [[data-uiify-" +
"input-group]>[data-part=addon]>&]:[max-inline-size:100%] [[data-uiify-input-group]>[data-part=addon]>&]:whi" +
"tespace-normal [[data-uiify-input-group]>[data-part=addon]>&]:[overflow-wrap:anywhere] [[data-uiify-item-co" +
"ntent]>&]:[justify-self:start]"
}
>
Save
</Button>
);
}/* app.css */
@layer reset, ui, app, tokens, base, demo, components, utilities, theme;
@import "@gunkin/uiify/styles/reset";
@import "@gunkin/uiify/components/behavior.css";
@import "@gunkin/uiify/styles/tokens";
@import "@gunkin/uiify/styles/tailwind";
@import "tailwindcss/utilities.css";
@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }
@source inline('focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [--uiify-button-rim:rgb(255_255_255_/_20%)] [--uiify-button-wash:light-dark(rgb(255_255_255_/_15%),_rgb(255_255_255_/_30%))] relative isolate inline-flex items-center justify-center [gap:0.375rem] [min-block-size:44px] [min-inline-size:44px] py-0 [padding-inline:1.25rem] border-[length:var(--border-width)] border-solid border-transparent rounded-[var(--radius-full)] bg-foreground bg-clip-padding text-background text-base font-[number:var(--weight-medium)] leading-6 no-underline cursor-pointer [transition:var(--transition-colors)] before:absolute before:inset-0 before:z-[-1] before:[border-radius:inherit] before:pointer-events-none after:absolute after:inset-0 after:z-[-1] after:[border-radius:inherit] after:pointer-events-none before:[background-color:var(--uiify-button-wash)] before:[filter:blur(10px)] before:[translate:0_-50%] before:[clip-path:inset(50%_0_-50%_0_round_var(--radius-full))] after:[padding:0.5px] after:[background-image:linear-gradient(to_bottom,_var(--uiify-button-rim),_transparent_75%)] after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [&:is([data-size=sm],[data-size=icon-sm])]:[min-block-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[min-inline-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[padding-inline:0.75rem] [&:is([data-size=sm],[data-size=icon-sm])]:[gap:0.25rem] [&:is([data-size=sm],[data-size=icon-sm])]:text-sm [&:is([data-size=sm],[data-size=icon-sm])]:leading-5 [&:is([data-size=lg],[data-size=icon-lg])]:[min-block-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[min-inline-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[padding-inline:1.75rem] [&:is([data-size=lg],[data-size=icon-lg])]:text-lg [&:is([data-size=lg],[data-size=icon-lg])]:leading-7 [&[data-size^=icon]]:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_90%,_var(--background))] active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_80%,_var(--background))] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:light-dark(rgb(0_0_0_/_5%),_rgb(255_255_255_/_10%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-wash:light-dark(rgb(255_255_255_/_20%),_rgb(255_255_255_/_5%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreground)_25%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:before:content-none data-[variant=outline]:after:[inset:calc(-1_*_var(--border-width))] data-[variant=outline]:after:[padding:var(--border-width)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=ghost]:before:content-none data-[variant=ghost]:after:content-none data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_26%,_transparent)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-wash:rgb(255_255_255_/_15%)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-destructive data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_20%)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_12%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:border-transparent [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:bg-primary [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-primary-foreground [:checked+&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [:checked+&]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [:checked+&]:border-transparent [:checked+&]:bg-primary [:checked+&]:text-primary-foreground [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true],[aria-current=page]):hover:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:not(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [:disabled+&]:opacity-50 [:disabled+&]:cursor-not-allowed [&[hidden]]:hidden data-[variant=link]:px-0 data-[variant=link]:border-0 data-[variant=link]:border-current data-[variant=link]:border-none data-[variant=link]:rounded-none data-[variant=link]:bg-transparent data-[variant=link]:text-primary data-[variant=link]:underline data-[variant=link]:[text-underline-offset:0.2em] data-[variant=link]:active:px-0 data-[variant=link]:active:border-0 data-[variant=link]:active:border-current data-[variant=link]:active:border-none data-[variant=link]:active:rounded-none data-[variant=link]:active:bg-transparent data-[variant=link]:active:text-primary data-[variant=link]:active:underline data-[variant=link]:active:[text-underline-offset:0.2em] data-[variant=link]:[min-inline-size:0] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-current [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:rounded-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:bg-transparent [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:text-primary [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:underline [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:[text-underline-offset:0.2em] data-[variant=link]:before:content-none data-[variant=link]:after:content-none data-[variant=link]:focus-visible:[outline:2px_solid_var(--ring)] data-[variant=link]:focus-visible:[outline-offset:3px] [[data-uiify-toggle]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-toggle-group]_[data-part=item]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-modal][data-part=close]&]:border-transparent [[data-uiify-modal][data-part=close]&]:[background-color:color-mix(in_oklch,_var(--foreground)_8%,_transparent)] [[data-uiify-modal][data-part=close]&]:text-foreground [[data-uiify-modal][data-part=close]&:hover]:[background-color:color-mix(in_oklch,_var(--foreground)_14%,_transparent)] [[data-uiify-modal][data-part=close]&:active]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [[data-uiify-pagination]_span&:not([data-disabled])]:cursor-default [[data-uiify-input-group]>[data-part=addon]>&]:[min-inline-size:0] [[data-uiify-input-group]>[data-part=addon]>&]:[max-inline-size:100%] [[data-uiify-input-group]>[data-part=addon]>&]:whitespace-normal [[data-uiify-input-group]>[data-part=addon]>&]:[overflow-wrap:anywhere] [[data-uiify-item-content]>&]:[justify-self:start]');
@source inline("before:content-[''] after:content-['']");
@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }
/* Authored animation and registered properties used by the Tailwind skin.
* Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
@keyframes uiify-presence-in {
from {
opacity: 0;
scale: var(--presence-scale);
}
}
}
@layer components {
@keyframes uiify-presence-out {
to {
opacity: 0;
scale: var(--presence-scale);
}
}
}
@property --uiify-spinner-tail {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
@property --uiify-spinner-length {
syntax: "<angle>";
inherits: false;
initial-value: 36deg;
}
@layer components {
@keyframes uiify-spinner-rotate {
to {
transform: rotate(360deg);
}
}
}
@layer components {
@keyframes uiify-spinner-orbit {
0% {
--uiify-spinner-tail: 0deg;
--uiify-spinner-length: 36deg;
}
50% {
--uiify-spinner-tail: 90deg;
--uiify-spinner-length: 324deg;
}
100% {
--uiify-spinner-tail: 360deg;
--uiify-spinner-length: 36deg;
}
}
}
@layer components {
@keyframes uiify-skeleton-pulse {
50% {
opacity: 0.45;
}
}
}
@layer components {
@keyframes uiify-skeleton-shimmer {
to {
background-position: -200% 0;
}
}
}
@layer components {
@keyframes uiify-skeleton-reveal {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
@property --candle-body-height {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --candle-body-top {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --candle-slot {
syntax: "<percentage>";
inherits: true;
initial-value: 100%;
}
@property --candle-wick-height {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --candle-wick-top {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --candle-x {
syntax: "<percentage>";
inherits: true;
initial-value: 0%;
}
@property --candle-scale {
syntax: "<number>";
inherits: true;
initial-value: 1;
}
@property --line-y {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --line-prev-y {
syntax: "<percentage>";
inherits: true;
initial-value: 50%;
}
@property --line-count {
syntax: "<integer>";
inherits: false;
initial-value: 1;
}
@property --line-scale {
syntax: "<number>";
inherits: true;
initial-value: 1;
}
@layer components {
@keyframes uiify-otp-input-caret-blink {
50% {
opacity: 0;
}
}
}⟦Éxámpléš~~~⟧
Variants
Pick a variant by emphasis. The default is the filled primary action — use one per view. secondary is a tonal fill for supporting actions, outline a bordered one, ghost shows a surface only on hover, destructive marks irreversible actions and link reads as inline text.
import { Button } from "@gunkin/uiify";
export default function Example() {
return (
<>
<Button>Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="link">Link</Button>
</>
);
}Sizes
sm is 32 px tall with 14 px text, md 44 px with 16 px and lg 56 px with 18 px. Omitting size gives md. Size and variant are independent, so every variant comes in every size.
import { Button } from "@gunkin/uiify";
export default function Example() {
return (
<>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</>
);
}With an icon
Put an icon next to the label as a child; the button spaces them 6 px apart. Mark decorative icons aria-hidden so the label stays the accessible name.
import { Button } from "@gunkin/uiify";
const plus = (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
</svg>
);
export default function Example() {
return (
<>
<Button>{plus}Create</Button>
<Button variant="secondary">{plus}Create</Button>
<Button variant="outline">{plus}Create</Button>
</>
);
}Selected
selected marks the chosen item of a set or a toggle that is on, in any variant, with the primary fill. Pass selected={false} for the off state of a toggle so it is still announced as one. Toggle, Toggle group, Segmented control, Pagination and Toolbar use this same state.
import { Button } from "@gunkin/uiify";
export default function Example() {
return (
<>
<Button variant="outline" selected>Bold</Button>
<Button variant="outline" selected={false}>Italic</Button>
<Button variant="ghost" selected>Bold</Button>
<Button variant="ghost" selected={false}>Italic</Button>
</>
);
}Disabled
disabled sets the native attribute on a <button>: it leaves the tab order, ignores clicks and shows no hover or pressed state. On any other element it sets aria-disabled and blocks activation instead.
import { Button } from "@gunkin/uiify";
export default function Example() {
return (
<>
<Button disabled>Default</Button>
<Button variant="secondary" disabled>Secondary</Button>
<Button variant="outline" disabled>Outline</Button>
<Button variant="destructive" disabled>Destructive</Button>
</>
);
}As a link
as="a" renders a real anchor, so navigation, middle-click and the link role all work natively. It keeps the button look in any variant; add variant="link" when it should read as text.
import { Button } from "@gunkin/uiify";
export default function Example() {
return (
<>
<Button as="a" href="/docs/installation">Get started</Button>
<Button as="a" href="/docs/installation" variant="outline">Installation</Button>
<Button as="a" href="/docs/installation" variant="link">Read the docs</Button>
</>
);
}⟦ÁPÍ~~⟧
Button
| ⟦Próp~~⟧ | ⟦Týpé~~⟧ | ⟦Défáúlt~~~⟧ | ⟦Déšçríptíóñ~~~~⟧ |
|---|---|---|---|
variant | "secondary" | "outline" | "ghost" | "destructive" | "link" | — | ⟦Víšúál émphášíš, wríttéñ tó dátá-váríáñt. Ómít ít fór thé fílléd prímárý áçtíóñ.~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
size | "sm" | "md" | "lg" | "icon-sm" | "icon" | "icon-lg" | — | ⟦Héíght áñd páddíñg thróúgh dátá-šížé (32, 44 ór 56 px, md bý défáúlt); íçóñ šížéš dróp šídé páddíñg áñd ñééd áríá-lábél.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
selected | boolean | — | ⟦Šétš dátá-šéléçtéd fór á çhóšéñ ítém ór áçtívé tógglé; á ñátívé <búttóñ> álšó gétš áríá-préššéd úñléšš áñóthér ÁRÍÁ štáté íš gívéñ.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
disabled | boolean | false | ⟦Díšábléš thé búttóñ: thé ñátívé áttríbúté óñ á <búttóñ>, áríá-díšábléd wíth blóçkéd áçtívátíóñ óñ áñý óthér éléméñt.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
type | "button" | "submit" | "reset" | "button" | ⟦Ñátívé búttóñ týpé, ápplíéd óñlý whéñ thé réñdéréd éléméñt íš á <búttóñ>. Šét "šúbmít" íñšídé á fórm.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
as | ElementType | "button" | ⟦Éléméñt ór çómpóñéñt tó réñdér, fór éxámplé "á" ór á róútér Líñk. Ít réçéívéš évérý próp áñd thé réf.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
render | ReactElement | ((props, state) => ReactElement) | — | ⟦Répláçé thé réñdéréd éléméñt whílé kéépíñg béhávíór áñd áççéššíbílítý wíríñg.~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
⟦Kéýbóárd~~~⟧
| ⟦Kéý~~⟧ | ⟦Áçtíóñ~~~⟧ |
|---|---|
| Enter / Space | ⟦Áçtíváté~~~⟧ |
| Tab | ⟦Fóçúš~~⟧ |
⟦Áççéššíbílítý~~~~~⟧
⟦Réñdérš á ñátívé <búttóñ týpé="búttóñ">: thé brówšér próvídéš fóçúš, Éñtér áñd Špáçé, áñd ít ñévér šúbmítš á fórm bý áççídéñt. Wíth áš ór réñdér ít kéépš thát éléméñt'š rólé, šó áñ áñçhór štáýš á líñk. díšábléd íš ñátívé óñ búttóñš áñd áríá-díšábléd wíth tábíñdéx="-1" élšéwhéré. šéléçtéd béçóméš áríá-préššéd; á šéléçtéd líñk ñéédš áríá-çúrréñt. Íçóñ-óñlý búttóñš ñééd áríá-lábél.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧
⟦Štýlíñg hóókš~~~~~⟧
data-uiify-buttondata-disableddata-selecteddata-variantdata-size--uiify-button-wash (colour of the glow in the upper half)--uiify-button-rim (colour of the top edge highlight)