Alert modal
⟦Díálóg thát áškš thé úšér tó çóñfírm ór çáñçél áñ áçtíóñ.~~~~~~~~~~~~~~~~~~~~⟧
- ⟦Brówšérš~~~⟧
- Chrome 123+Firefox 121+Safari 17.5+
- ⟦Tíér~~⟧
- 0
- ⟦Búñdlé çómpréššéd~~~~~~⟧
- 555 B
- ⟦Fórçéd láýóút búdgét~~~~~~~⟧
- 0 ms
- ⟦Áxé búdgét~~~~⟧
- 0
- ⟦ŠŠR çómpátíblé~~~~~⟧
- ⟦Ýéš~~⟧
- ⟦Búílt óñ~~~⟧
- elements/dialog
- ⟦Públíšhéd çóñtráçt~~~~~~~⟧
- ⟦Téšt hárñéšš~~~~~⟧
⟦Pláýgróúñd~~~~⟧
Alert Modal
Closed
import { AlertModal } from "@gunkin/uiify";
export default function Example() {
return (
<div className="">
<AlertModal
id="delete-dialog"
trigger="Delete item"
title="Delete item?"
description="This action cannot be undone."
/>
</div>
);
}/* 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;
}
}
/* Native dialog surfaces. */
@layer components {
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
) {
margin: auto;
inline-size: min(28rem, calc(100vw - 2 * var(--space-lg)));
padding: var(--space-xl);
border: var(--border-width) solid var(--border);
/* One-off: the modal is the highest-elevation surface in the 2a/2b
handoff (uiify Components Kit.dc.html), rounder than the --radius-lg
card scale (12px) that every other bordered container uses. */
border-radius: var(--radius-xl);
corner-shape: squircle;
background-color: var(--popover);
color: var(--popover-foreground);
box-shadow: var(--shadow-modal);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
)::backdrop {
background-color: rgb(0 0 0 / 45%);
}
:where([data-uiify-dialog], [data-uiify-modal], [data-uiify-alert-modal], [data-uiify-drawer])
[data-part="title"] {
margin: 0;
font-size: var(--text-xl);
font-weight: var(--weight-semibold);
line-height: 1.3;
}
:where([data-uiify-dialog], [data-uiify-modal], [data-uiify-alert-modal], [data-uiify-drawer])
[data-part="description"] {
margin-block: var(--space-sm) var(--space-lg);
color: var(--muted-foreground);
font-size: var(--text-sm);
line-height: var(--leading-normal);
}
@media (prefers-reduced-motion: no-preference) {
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
),
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
)::backdrop {
transition: var(--transition-presence);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"]
):not([open]) {
opacity: 0;
scale: var(--presence-scale);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
):not([open])::backdrop {
opacity: 0;
}
@starting-style {
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"]
)[open] {
opacity: 0;
scale: var(--presence-scale);
}
:where(
[data-uiify-dialog][data-part="content"],
[data-uiify-modal][data-part="content"],
[data-uiify-alert-modal][data-part="content"],
[data-uiify-drawer][data-part="content"]
)[open]::backdrop {
opacity: 0;
}
}
}
/* Modal anatomy. AlertModal, Drawer and Dialog keep the shared surface above; every rule
below is scoped to `[data-uiify-modal]`, so their look is unchanged.
The column layout sits on the base rule, not under [open]: on close `display` stays flex
through the exit transition (allow-discrete), so the dialog must keep its column until it
is hidden. Only `display` depends on [open]. */
[data-uiify-modal][data-part="content"] {
--uiify-modal-inline-size: 28rem;
flex-direction: column;
gap: var(--space-md);
inline-size: min(var(--uiify-modal-inline-size), calc(100vw - 2 * var(--space-lg)));
max-block-size: calc(100dvh - 2 * var(--space-lg));
}
[data-uiify-modal][data-part="content"][open] {
display: flex;
}
[data-uiify-modal][data-part="content"][data-size="sm"] {
--uiify-modal-inline-size: 24rem;
}
[data-uiify-modal][data-part="content"][data-size="lg"] {
--uiify-modal-inline-size: 40rem;
}
[data-uiify-modal][data-part="content"][data-size="full"] {
inset: 0;
margin: 0;
inline-size: 100%;
block-size: 100%;
max-inline-size: none;
max-block-size: none;
padding-block: max(var(--space-xl), env(safe-area-inset-top))
max(var(--space-xl), env(safe-area-inset-bottom));
padding-inline: max(var(--space-xl), env(safe-area-inset-left))
max(var(--space-xl), env(safe-area-inset-right));
border: 0;
border-radius: 0;
}
/* Below 40rem the bottom sheet wins, so align=start only applies from the breakpoint up. */
@media (width >= 40rem) {
[data-uiify-modal][data-part="content"][data-align="start"]:not([data-size="full"]) {
margin-block: var(--space-xl) auto;
}
}
[data-uiify-modal][data-part="header"] {
position: relative;
flex: none;
padding-inline-end: 2.5rem;
}
/* Every close button shares one soft look: a filled, borderless surface that steps up on
hover and press. The header icon is a circle; text closers (footer, body) keep the pill. */
[data-uiify-modal][data-part="close"][data-uiify-button] {
border-color: transparent;
background-color: color-mix(in oklch, var(--foreground) 8%, transparent);
color: var(--foreground);
}
[data-uiify-modal][data-part="close"][data-uiify-button]:hover {
background-color: color-mix(in oklch, var(--foreground) 14%, transparent);
}
[data-uiify-modal][data-part="close"][data-uiify-button]:active {
background-color: color-mix(in oklch, var(--foreground) 20%, transparent);
}
[data-uiify-modal][data-part="header"] > [data-part="close"] {
position: absolute;
inset-block-start: 0;
inset-inline-end: 0;
display: grid;
place-items: center;
inline-size: 2rem;
block-size: 2rem;
min-block-size: 0;
padding: 0;
border-radius: 999px;
}
[data-uiify-modal][data-part="body"] {
flex: 1 1 auto;
min-block-size: 0;
overflow: auto;
overscroll-behavior: contain;
}
[data-uiify-modal][data-part="footer"] {
display: flex;
flex: none;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-sm);
}
@media (pointer: coarse) {
[data-uiify-modal][data-part="header"] {
padding-inline-end: 2.75rem;
}
[data-uiify-modal][data-part="header"] > [data-part="close"] {
min-inline-size: 2.75rem;
min-block-size: 2.75rem;
}
[data-uiify-modal][data-part="footer"] > * {
min-block-size: 2.75rem;
}
}
/* Mobile sheet below 40rem (keep in sync with SHEET_QUERY in enhance/sheet-swipe.ts):
at least 50dvh and at most 80dvh, so the top 20% of the viewport is always backdrop. */
[data-uiify-modal][data-part="grabber"] {
display: none;
flex: none;
block-size: 1.5rem;
margin-block: calc(-1 * var(--space-md)) calc(-1 * var(--space-sm));
touch-action: none;
}
[data-uiify-modal][data-part="grabber"]::after {
content: "";
display: block;
inline-size: 2.25rem;
block-size: 0.25rem;
margin-block: 0.5rem 0;
margin-inline: auto;
border-radius: 999px;
background-color: var(--border);
}
@media (width < 40rem) {
[data-uiify-modal][data-part="content"]:not([data-size="full"]) {
inline-size: 100%;
max-inline-size: 100%;
min-block-size: 50dvh;
max-block-size: 80dvh;
margin-block: auto 0;
margin-inline: 0;
padding-block-end: calc(var(--space-xl) + env(safe-area-inset-bottom));
border-block-end-width: 0;
border-end-start-radius: 0;
border-end-end-radius: 0;
}
/* With a ModalBody the body is the scroller, so the rest of the sheet is a swipe surface.
Without one the dialog itself scrolls and keeps the default touch action; the header and
grabber stay swipe handles either way (touch-action applies to the element touched). */
[data-uiify-modal][data-part="content"]:not([data-size="full"]):has(
> [data-uiify-modal][data-part="body"]
) {
touch-action: none;
}
[data-uiify-modal][data-part="content"]:not([data-size="full"])
[data-uiify-modal][data-part="header"] {
touch-action: none;
}
[data-uiify-modal][data-part="body"] {
touch-action: pan-y;
}
[data-uiify-modal][data-part="content"]:not([data-size="full"]) > [data-part="grabber"] {
display: block;
}
}
@media (width < 40rem) and (prefers-reduced-motion: no-preference) {
[data-uiify-modal][data-part="content"]:not([data-size="full"]) {
transition:
translate var(--duration-fast) var(--ease-out),
opacity var(--duration-fast) var(--ease-out),
display var(--duration-fast) allow-discrete,
overlay var(--duration-fast) allow-discrete;
}
[data-uiify-modal][data-part="content"]:not([data-size="full"], [open]) {
translate: 0 100%;
scale: 1;
}
@starting-style {
[data-uiify-modal][data-part="content"]:not([data-size="full"])[open] {
translate: 0 100%;
scale: 1;
}
}
}
/* While a finger drives the sheet (see enhance/sheet-swipe.ts): no transition, position
from the offset. Removing the attribute hands the position back to the rules above,
which animates the sheet out or back. */
[data-uiify-modal][data-part="content"][data-dragging][open]:not([data-size="full"]) {
transition: none;
translate: 0 var(--uiify-sheet-offset, 0);
}
[data-uiify-modal][data-part="content"][data-backdrop="blur"]::backdrop {
backdrop-filter: blur(4px);
}
[data-uiify-modal][data-part="content"][data-backdrop="none"]::backdrop {
background-color: transparent;
}
/* Nested modals: the child is a DOM descendant of the parent, so CSS can see it. The
parent's own backdrop keeps dimming the page (one layer); the parent steps back. */
[data-uiify-modal][data-part="content"] [data-uiify-modal][data-part="content"]::backdrop {
background-color: transparent;
backdrop-filter: none;
}
[data-uiify-modal][data-part="content"]:has([data-uiify-modal][data-part="content"]:modal) {
filter: brightness(0.9);
}
@media (width >= 40rem) {
[data-uiify-modal][data-part="content"]:has([data-uiify-modal][data-part="content"]:modal) {
scale: 0.97;
}
}
}import { AlertModal } from "@gunkin/uiify";
export default function Example() {
return (
<div
className="
[&[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)]
[&_[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)]
[&[popover]:popover-open:not([data-positioning=native])]:[margin:revert]
[&_[popover]:popover-open:not([data-positioning=native])]:[margin:revert] [&[data-part=content]]:m-auto
[&[data-part=content]]:[inline-size:min(28rem,_calc(100vw_-_2_*_var(--space-lg)))] [&[data-part=content]]:p-xl
[&[data-part=content]]:border-[length:var(--border-width)] [&[data-part=content]]:border-solid
[&[data-part=content]]:border-border [&[data-part=content]]:rounded-xl
[&[data-part=content]]:[corner-shape:squircle] [&[data-part=content]]:bg-popover
[&[data-part=content]]:text-popover-foreground [&[data-part=content]]:shadow-modal
[&[data-part=content]::backdrop]:[background-color:rgb(0_0_0_/_45%)] [&_[data-part=title]]:m-0
[&_[data-part=title]]:text-[length:var(--text-xl)] [&_[data-part=title]]:font-[number:var(--weight-semibold)]
[&_[data-part=title]]:[line-height:1.3]
[&_[data-part=description]]:[margin-block:var(--space-sm)_var(--space-lg)]
[&_[data-part=description]]:text-muted-foreground [&_[data-part=description]]:text-sm
[&_[data-part=description]]:[line-height:var(--leading-normal)]
motion-safe:[&[data-part=content]]:[transition:var(--transition-presence)]
motion-safe:[&[data-part=content]::backdrop]:[transition:var(--transition-presence)]
motion-safe:[&[data-part=content]:not([open])]:opacity-0
motion-safe:[&[data-part=content]:not([open])]:[scale:var(--presence-scale)]
motion-safe:[&[data-part=content]:not([open])::backdrop]:opacity-0
motion-safe:starting:[&[data-part=content][open]]:opacity-0
motion-safe:starting:[&[data-part=content][open]]:[scale:var(--presence-scale)]
motion-safe:starting:[&[data-part=content][open]::backdrop]:opacity-0 data-[part=content]:gap-md
data-[part=content]:hidden data-[part=content]:[&[open]]:grid [&_[data-part=footer]]:flex
[&_[data-part=footer]]:justify-end [&_[data-part=footer]]:gap-sm [&_[data-part=footer]]:pt-md
[&_[data-part=footer]]:[border-block-start-width:1px] [&_[data-part=footer]]:[border-block-start-style:solid]
[&_[data-part=footer]]:[border-block-start-color:var(--border)]
"
>
<AlertModal
id="delete-dialog"
trigger="Delete item"
title="Delete item?"
description="This action cannot be undone."
/>
</div>
);
}/* 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('[&[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)] [&_[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)] [&[popover]:popover-open:not([data-positioning=native])]:[margin:revert] [&_[popover]:popover-open:not([data-positioning=native])]:[margin:revert] [&[data-part=content]]:m-auto [&[data-part=content]]:[inline-size:min(28rem,_calc(100vw_-_2_*_var(--space-lg)))] [&[data-part=content]]:p-xl [&[data-part=content]]:border-[length:var(--border-width)] [&[data-part=content]]:border-solid [&[data-part=content]]:border-border [&[data-part=content]]:rounded-xl [&[data-part=content]]:[corner-shape:squircle] [&[data-part=content]]:bg-popover [&[data-part=content]]:text-popover-foreground [&[data-part=content]]:shadow-modal [&[data-part=content]::backdrop]:[background-color:rgb(0_0_0_/_45%)] [&_[data-part=title]]:m-0 [&_[data-part=title]]:text-[length:var(--text-xl)] [&_[data-part=title]]:font-[number:var(--weight-semibold)] [&_[data-part=title]]:[line-height:1.3] [&_[data-part=description]]:[margin-block:var(--space-sm)_var(--space-lg)] [&_[data-part=description]]:text-muted-foreground [&_[data-part=description]]:text-sm [&_[data-part=description]]:[line-height:var(--leading-normal)] motion-safe:[&[data-part=content]]:[transition:var(--transition-presence)] motion-safe:[&[data-part=content]::backdrop]:[transition:var(--transition-presence)] motion-safe:[&[data-part=content]:not([open])]:opacity-0 motion-safe:[&[data-part=content]:not([open])]:[scale:var(--presence-scale)] motion-safe:[&[data-part=content]:not([open])::backdrop]:opacity-0 motion-safe:starting:[&[data-part=content][open]]:opacity-0 motion-safe:starting:[&[data-part=content][open]]:[scale:var(--presence-scale)] motion-safe:starting:[&[data-part=content][open]::backdrop]:opacity-0 data-[part=content]:gap-md data-[part=content]:hidden data-[part=content]:[&[open]]:grid [&_[data-part=footer]]:flex [&_[data-part=footer]]:justify-end [&_[data-part=footer]]:gap-sm [&_[data-part=footer]]:pt-md [&_[data-part=footer]]:[border-block-start-width:1px] [&_[data-part=footer]]:[border-block-start-style:solid] [&_[data-part=footer]]:[border-block-start-color:var(--border)]');
@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;
}
}
}⟦ÁPÍ~~⟧
AlertModal
| ⟦Próp~~⟧ | ⟦Týpé~~⟧ | ⟦Défáúlt~~~⟧ | ⟦Déšçríptíóñ~~~~⟧ |
|---|---|---|---|
id* | string | — | ⟦Úñíqúé íd fór thé álértdíálóg éléméñt, tárgétéd bý thé tríggér, çáñçél áñd áçtíóñ búttóñš.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
trigger* | ReactNode | — | ⟦Çóñtéñt óf thé búttóñ thát ópéñš thé díálóg.~~~~~~~~~~~~~~~~⟧ |
title* | ReactNode | — | ⟦Héádíñg réñdéréd íñšídé thé díálóg áñd úšéd áš ítš áççéššíblé ñámé.~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
description | ReactNode | — | ⟦Šúppórtíñg téxt réñdéréd úñdér thé títlé áñd référéñçéd bý áríá-déšçríbédbý.~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
children | ReactNode | — | ⟦Bódý çóñtéñt pláçéd bétwééñ thé déšçríptíóñ áñd thé twó búttóñš.~~~~~~~~~~~~~~~~~~~~~~~⟧ |
actionLabel | ReactNode | "Confirm" | ⟦Çóñtéñt óf thé çóñfírmíñg búttóñ, whíçh çlóšéš wíth thé válúé "çóñfírm".~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
cancelLabel | ReactNode | "Cancel" | ⟦Çóñtéñt óf thé díšmíššíñg búttóñ, whíçh çlóšéš wíth thé válúé "çáñçél".~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
className | string | — | ⟦Çlášš ápplíéd tó thé díálóg éléméñt.~~~~~~~~~~~~~⟧ |
AlertModalTrigger
| ⟦Próp~~⟧ | ⟦Týpé~~⟧ | ⟦Défáúlt~~~⟧ | ⟦Déšçríptíóñ~~~~⟧ |
|---|---|---|---|
target* | string | — | ⟦Íd óf thé ÁlértMódálÇóñtéñt thíš búttóñ ópéñš; réšólvéd bý thé brówšér át çlíçk tímé.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
children | ReactNode | — | ⟦Çóñtéñt óf thé tríggér búttóñ.~~~~~~~~~~~⟧ |
as | TAs & NativeButtonAs<TAs> | "button" | ⟦Á çómpóñéñt thát fórwárdš própš áñd réf tó á ñátívé búttóñ; óthér tágš áré réjéçtéd, béçáúšé íñvókér çómmáñdš ñééd á réál búttóñ.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
AlertModalContent
| ⟦Próp~~⟧ | ⟦Týpé~~⟧ | ⟦Défáúlt~~~⟧ | ⟦Déšçríptíóñ~~~~⟧ |
|---|---|---|---|
id* | string | — | ⟦Úñíqúé íd šúpplíéd bý thé çóñšúmér; référéñçéd bý thé óthér pártš.~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
children | ReactNode | — | ⟦Çóñtéñt óf thé díálóg, whíçh íš réñdéréd wíth rólé="álértdíálóg".~~~~~~~~~~~~~~~~~~~~~~~⟧ |
AlertModalAction
| ⟦Próp~~⟧ | ⟦Týpé~~⟧ | ⟦Défáúlt~~~⟧ | ⟦Déšçríptíóñ~~~~⟧ |
|---|---|---|---|
target* | string | — | ⟦Íd óf thé ÁlértMódálÇóñtéñt thíš búttóñ çlóšéš tó çóñfírm.~~~~~~~~~~~~~~~~~~~~~⟧ |
children | ReactNode | — | ⟦Çóñtéñt óf thé çóñfírmíñg búttóñ.~~~~~~~~~~~~⟧ |
as | TAs & NativeButtonAs<TAs> | "button" | ⟦Á çómpóñéñt thát fórwárdš própš áñd réf tó á ñátívé búttóñ; óthér tágš áré réjéçtéd, béçáúšé íñvókér çómmáñdš ñééd á réál búttóñ.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
AlertModalCancel
| ⟦Próp~~⟧ | ⟦Týpé~~⟧ | ⟦Défáúlt~~~⟧ | ⟦Déšçríptíóñ~~~~⟧ |
|---|---|---|---|
target* | string | — | ⟦Íd óf thé ÁlértMódálÇóñtéñt thíš búttóñ çlóšéš tó díšmíšš.~~~~~~~~~~~~~~~~~~~~~⟧ |
children | ReactNode | — | ⟦Çóñtéñt óf thé díšmíššíñg búttóñ.~~~~~~~~~~~~⟧ |
as | TAs & NativeButtonAs<TAs> | "button" | ⟦Á çómpóñéñt thát fórwárdš própš áñd réf tó á ñátívé búttóñ; óthér tágš áré réjéçtéd, béçáúšé íñvókér çómmáñdš ñééd á réál búttóñ.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧ |
⟦Kéýbóárd~~~⟧
| ⟦Kéý~~⟧ | ⟦Áçtíóñ~~~⟧ |
|---|---|
| Tab | ⟦Móvé fóçúš tó/fróm thé çóñtról~~~~~~~~~~~⟧ |
| Enter / Space | ⟦Áçtíváté whéñ fóçúšéd (búttóñ-líké çóñtrólš)~~~~~~~~~~~~~~~~⟧ |
⟦Áççéššíbílítý~~~~~⟧
⟦Úšéš šémáñtíç HTML whéré póššíblé. Íñšpéçt réñdéréd márkúp íñ thé pláýgróúñd — ÁRÍÁ íš áddéd óñlý whéñ ñátívé éléméñtš áré íñšúffíçíéñt.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~⟧
⟦Štýlíñg hóókš~~~~~⟧
data-disabled