Alert modal
תיבת דו-שיח שמבקשת מהמשתמש לאשר או לבטל פעולה.
- דפדפנים
- Chrome 123+Firefox 121+Safari 17.5+
- שכבה
- 0
- גודל חבילה דחוסה
- 599 B
- מגבלת פריסה כפויה
- 0 ms
- מגבלת Axe
- 0
- תואם SSR
- כן
- מבוסס על
- elements/dialog
- חוזה מפורסם
- סביבת בדיקות
מגרש ניסויים
import { AlertModal } from "@gunkin/uiify";
export default function Example() {
return (
<AlertModal
id="delete-dialog"
trigger="Delete item"
title="Delete item?"
description="This action cannot be undone."
slotProps={{
trigger: { className: "" },
content: { className: "" },
title: { className: "" },
description: { className: "" },
cancel: { className: "" },
action: { className: "" },
}}
/>
);
}/* 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: var(--control-md);
/* Never narrower than tall: an icon size only has to drop the padding to be a circle. */
min-inline-size: var(--control-md);
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: var(--control-sm);
min-inline-size: var(--control-sm);
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: var(--control-lg);
min-inline-size: var(--control-lg);
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);
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"] {
flex: none;
padding-inline-end: var(--control-md);
}
[data-uiify-modal][data-part="header"] > [data-part="close"] {
position: absolute;
inset-block-start: 10px;
inset-inline-end: 10px;
display: grid;
place-items: center;
}
/* Close glyph: two crossed bars on the ghost Button's free pseudo-elements. `all: unset`
drops the geometry Button gives them; both share the centred grid cell. */
[data-uiify-modal][data-part="header"] > [data-part="close"]:empty {
&::before,
&::after {
all: unset;
content: "";
grid-area: 1 / 1;
inline-size: 0.875rem;
block-size: 2px;
border-radius: 1px;
background-color: currentcolor;
}
&::before {
rotate: 45deg;
}
&::after {
rotate: -45deg;
}
}
[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);
}
/* 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 (
<AlertModal
id="delete-dialog"
trigger="Delete item"
title="Delete item?"
description="This action cannot be undone."
slotProps={{
trigger: {
className:
"relative isolate inline-flex items-center justify-center gap-1.5 min-h-10 " +
"min-w-10 px-5 py-px border-[length:var(--border-width)] border-solid " +
"border-transparent rounded-full bg-clip-padding text-base font-medium leading-6 " +
"no-underline cursor-pointer [transition:var(--transition-colors)] " +
"focus-visible:[outline:none] " +
"focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_20%,_transparent)] " +
"disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
"data-[disabled]:cursor-not-allowed " +
"[&: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))] " +
"bg-foreground text-background " +
"[@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))]",
},
content: {
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]]:bg-popover " +
"[&[data-part=content]]:text-popover-foreground " +
"[&[data-part=content]]:shadow-modal " +
"[&[data-part=content]::backdrop]:[background-color:rgb(0_0_0_/_45%)] " +
"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",
},
title: {
className: "m-0 text-[length:var(--text-xl)] font-[number:var(--weight-semibold)] " + "[line-height:1.3]",
},
description: {
className:
"[margin-block:var(--space-sm)_var(--space-lg)] text-muted-foreground text-sm " +
"[line-height:var(--leading-normal)]",
},
cancel: {
className:
"relative isolate inline-flex items-center justify-center gap-1.5 min-h-10 " +
"min-w-10 px-5 py-px border-[length:var(--border-width)] border-solid " +
"border-transparent rounded-full bg-clip-padding text-base font-medium leading-6 " +
"no-underline cursor-pointer [transition:var(--transition-colors)] " +
"focus-visible:[outline:none] " +
"focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_20%,_transparent)] " +
"disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
"data-[disabled]:cursor-not-allowed " +
"[&: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))] " +
"bg-transparent text-foreground before:content-none after:content-none " +
"hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] " +
"active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)]",
},
action: {
className:
"relative isolate inline-flex items-center justify-center gap-1.5 min-h-10 " +
"min-w-10 px-5 py-px border-[length:var(--border-width)] border-solid " +
"border-transparent rounded-full bg-clip-padding text-base font-medium leading-6 " +
"no-underline cursor-pointer [transition:var(--transition-colors)] " +
"focus-visible:[outline:none] " +
"focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_20%,_transparent)] " +
"disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 " +
"data-[disabled]:cursor-not-allowed " +
"[&: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))] " +
"[--uiify-button-wash:rgb(255_255_255_/_15%)] bg-destructive " +
"text-destructive-foreground " +
"hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_12%)] " +
"active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_20%)]",
},
}}
/>
);
}API
AlertModal
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
id* | string | — | מזהה ייחודי לאלמנט alertdialog שאליו מכוונים כפתורי הפתיחה, הביטול והפעולה. |
trigger* | ReactNode | — | תוכן הכפתור שפותח את תיבת הדו-שיח. |
title* | ReactNode | — | כותרת המוצגת בתוך תיבת הדו-שיח ומשמשת כשמה הנגיש. |
description | ReactNode | — | טקסט משלים המוצג מתחת לכותרת ומקושר באמצעות aria-describedby. |
children | ReactNode | — | תוכן הגוף המוצב בין התיאור לשני הכפתורים. |
actionLabel | ReactNode | "Confirm" | תוכן כפתור האישור, הסוגר עם הערך "confirm". |
cancelLabel | ReactNode | "Cancel" | תוכן כפתור הביטול, הסוגר עם הערך "cancel". |
className | string | — | המחלקה המוחלת על אלמנט השורש של AlertModal. |
slotProps | AlertModalSlotProps | — | מאפיינים מובנים לחלקים ש-AlertModal יוצר; השורש שלו מקבל מאפיינים מובנים ישירות. |
AlertModalTrigger
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
target* | string | — | מזהה ה-AlertModalContent שכפתור זה פותח; הדפדפן מאתר אותו בזמן הלחיצה. |
children | ReactNode | — | תוכן כפתור הפתיחה. |
as | TAs & NativeButtonAs<TAs> | "button" | רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי. |
AlertModalContent
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
id* | string | — | מזהה ייחודי שמספק המשתמש בספרייה; החלקים האחרים מפנים אליו. |
children | ReactNode | — | תוכן תיבת הדו-שיח, המוצגת עם role="alertdialog". |
AlertModalAction
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
target* | string | — | מזהה ה-AlertModalContent שכפתור זה סוגר לצורך אישור. |
children | ReactNode | — | תוכן כפתור האישור. |
as | TAs & NativeButtonAs<TAs> | "button" | רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי. |
AlertModalCancel
| מאפיין | טיפוס | ברירת מחדל | תיאור |
|---|---|---|---|
target* | string | — | מזהה ה-AlertModalContent שכפתור זה סוגר לצורך ביטול. |
children | ReactNode | — | תוכן כפתור הביטול. |
as | TAs & NativeButtonAs<TAs> | "button" | רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי. |
מקלדת
| מקש | פעולה |
|---|---|
| Tab | העברת המיקוד אל הפקד או ממנו |
| Enter / Space | הפעלה כשהפקד ממוקד (פקדים דמויי כפתור) |
נגישות
משתמש ב-HTML סמנטי ככל האפשר. בדקו את הסימון המרונדר במגרש הניסויים — ARIA נוסף רק כאשר האלמנטים המובנים אינם מספיקים.
נקודות חיבור לעיצוב
data-disabled