דילוג לתוכן
uiify

Alert modal

תיבת דו-שיח שמבקשת מהמשתמש לאשר או לבטל פעולה.

דפדפנים
Chrome 123+Firefox 121+Safari 17.5+
שכבה
0
גודל חבילה דחוסה
599 B
מגבלת פריסה כפויה
0 ms
מגבלת Axe
0
תואם SSR
כן
מבוסס על
elements/dialog
חוזה מפורסם
סביבת בדיקות

מגרש ניסויים

Alert Modal

Closed

Delete item?

אי אפשר לבטל פעולה זו.

מחיקת פריט

אי אפשר לבטל פעולה זו.

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—כותרת המוצגת בתוך תיבת הדו-שיח ומשמשת כשמה הנגיש.
descriptionReactNode—טקסט משלים המוצג מתחת לכותרת ומקושר באמצעות aria-describedby.
childrenReactNode—תוכן הגוף המוצב בין התיאור לשני הכפתורים.
actionLabelReactNode"Confirm"תוכן כפתור האישור, הסוגר עם הערך "confirm".
cancelLabelReactNode"Cancel"תוכן כפתור הביטול, הסוגר עם הערך "cancel".
classNamestring—המחלקה המוחלת על אלמנט השורש של AlertModal.
slotPropsAlertModalSlotProps—מאפיינים מובנים לחלקים ש-AlertModal יוצר; השורש שלו מקבל מאפיינים מובנים ישירות.

AlertModalTrigger

מאפייןטיפוסברירת מחדלתיאור
target*string—מזהה ה-AlertModalContent שכפתור זה פותח; הדפדפן מאתר אותו בזמן הלחיצה.
childrenReactNode—תוכן כפתור הפתיחה.
asTAs & NativeButtonAs<TAs>"button"רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי.

AlertModalContent

מאפייןטיפוסברירת מחדלתיאור
id*string—מזהה ייחודי שמספק המשתמש בספרייה; החלקים האחרים מפנים אליו.
childrenReactNode—תוכן תיבת הדו-שיח, המוצגת עם role="alertdialog".

AlertModalAction

מאפייןטיפוסברירת מחדלתיאור
target*string—מזהה ה-AlertModalContent שכפתור זה סוגר לצורך אישור.
childrenReactNode—תוכן כפתור האישור.
asTAs & NativeButtonAs<TAs>"button"רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי.

AlertModalCancel

מאפייןטיפוסברירת מחדלתיאור
target*string—מזהה ה-AlertModalContent שכפתור זה סוגר לצורך ביטול.
childrenReactNode—תוכן כפתור הביטול.
asTAs & NativeButtonAs<TAs>"button"רכיב המעביר מאפיינים ו-ref לכפתור מובנה; תגיות אחרות נדחות, כי פקודות הפעלה דורשות כפתור אמיתי.

מקלדת

מקשפעולה
Tabהעברת המיקוד אל הפקד או ממנו
Enter / Spaceהפעלה כשהפקד ממוקד (פקדים דמויי כפתור)

נגישות

משתמש ב-HTML סמנטי ככל האפשר. בדקו את הסימון המרונדר במגרש הניסויים — ARIA נוסף רק כאשר האלמנטים המובנים אינם מספיקים.

נקודות חיבור לעיצוב

data-disabled
דף זה תורגם אוטומטית וטרם נבדק.