דילוג לתוכן
uiify

Modal

תיבת דו-שיח מודאלית המבוססת על אלמנט הדו-שיח המובנה.

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

מגרש ניסויים

Small

24rem wide.

Large

40rem wide.

Terms of service

The whole document scrolls together.

Clause 1: text that makes the document taller than the screen.

Clause 2: text that makes the document taller than the screen.

Clause 3: text that makes the document taller than the screen.

Clause 4: text that makes the document taller than the screen.

Clause 5: text that makes the document taller than the screen.

Clause 6: text that makes the document taller than the screen.

Clause 7: text that makes the document taller than the screen.

Clause 8: text that makes the document taller than the screen.

Clause 9: text that makes the document taller than the screen.

Clause 10: text that makes the document taller than the screen.

Clause 11: text that makes the document taller than the screen.

Clause 12: text that makes the document taller than the screen.

Clause 13: text that makes the document taller than the screen.

Clause 14: text that makes the document taller than the screen.

Clause 15: text that makes the document taller than the screen.

Clause 16: text that makes the document taller than the screen.

Clause 17: text that makes the document taller than the screen.

Clause 18: text that makes the document taller than the screen.

Clause 19: text that makes the document taller than the screen.

Clause 20: text that makes the document taller than the screen.

Clause 21: text that makes the document taller than the screen.

Clause 22: text that makes the document taller than the screen.

Clause 23: text that makes the document taller than the screen.

Clause 24: text that makes the document taller than the screen.

Clause 25: text that makes the document taller than the screen.

Clause 26: text that makes the document taller than the screen.

Clause 27: text that makes the document taller than the screen.

Clause 28: text that makes the document taller than the screen.

Clause 29: text that makes the document taller than the screen.

Clause 30: text that makes the document taller than the screen.

Edit profile

Unsaved changes are lost when you close.

Discard changes?

This cannot be undone.

הגדרות

עדכון העדפות הפרופיל שלך.

import { Modal } from "@gunkin/uiify";

export default function Example() {
  return (
    <Modal
      id="settings-dialog"
      trigger="Open settings"
      title="Settings"
      description="Update your profile preferences."
      slotProps={{
        trigger: { className: "" },
        content: { className: "" },
        grabber: { className: "" },
        header: { className: "" },
        title: { className: "" },
        description: { className: "" },
        close: { className: " " },
        body: { className: "" },
        footer: { className: "" },
      }}
    />
  );
}
/* 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 { Modal } from "@gunkin/uiify";

export default function Example() {
  return (
    <Modal
      id="settings-dialog"
      trigger="Open settings"
      title="Settings"
      description="Update your profile preferences."
      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]:[--uiify-modal-inline-size:28rem] " +
            "data-[part=content]:flex-col data-[part=content]:gap-md " +
            "data-[part=content]:[inline-size:min(var(--uiify-modal-inline-size),_calc(100vw_-_2_*_var(--space-lg)))] " +
            "data-[part=content]:[max-block-size:calc(100dvh_-_2_*_var(--space-lg))] " +
            "data-[part=content]:[&[open]]:flex " +
            "data-[part=content]:data-[size=sm]:[--uiify-modal-inline-size:24rem] " +
            "data-[part=content]:data-[size=lg]:[--uiify-modal-inline-size:40rem] " +
            "data-[part=content]:data-[size=full]:inset-0 " +
            "data-[part=content]:data-[size=full]:m-0 " +
            "data-[part=content]:data-[size=full]:[inline-size:100%] " +
            "data-[part=content]:data-[size=full]:[block-size:100%] " +
            "data-[part=content]:data-[size=full]:[max-inline-size:none] " +
            "data-[part=content]:data-[size=full]:[max-block-size:none] " +
            "data-[part=content]:data-[size=full]:[padding-block:max(var(--space-xl),_env(safe-area-inset-top))_max(var(--space-xl),_env(safe-area-inset-bottom))] " +
            "data-[part=content]:data-[size=full]:[padding-inline:max(var(--space-xl),_env(safe-area-inset-left))_max(var(--space-xl),_env(safe-area-inset-right))] " +
            "data-[part=content]:data-[size=full]:border-0 " +
            "data-[part=content]:data-[size=full]:border-current " +
            "data-[part=content]:data-[size=full]:border-none " +
            "data-[part=content]:data-[size=full]:rounded-none " +
            "[@media_(width_>=_40rem)]:data-[part=content]:data-[align=start]:[&:not([data-size=full])]:[margin-block:var(--space-xl)_auto] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[inline-size:100%] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[max-inline-size:100%] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[min-block-size:50dvh] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[max-block-size:80dvh] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[margin-block:auto_0] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[margin-inline:0] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[padding-block-end:calc(var(--space-xl)_+_env(safe-area-inset-bottom))] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[border-block-end-width:0] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[border-end-start-radius:0] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])]:[border-end-end-radius:0] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full]):has(>[data-uiify-modal][data-part=body])]:[touch-action:none] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])_[data-uiify-modal][data-part=header]]:[touch-action:none] " +
            "[@media_(width_<_40rem)]:data-[part=content]:[&:not([data-size=full])>[data-part=grabber]]:block " +
            "[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]: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] " +
            "[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:data-[part=content]:[&:not([data-size=full],[open])]:[translate:0_100%] " +
            "[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:data-[part=content]:[&:not([data-size=full],[open])]:[scale:1] " +
            "[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:starting:data-[part=content]:[&:not([data-size=full])[open]]:[translate:0_100%] " +
            "[@media_(width_<_40rem)_and_(prefers-reduced-motion:_no-preference)]:starting:data-[part=content]:[&:not([data-size=full])[open]]:[scale:1] " +
            "data-[part=content]:data-[dragging]:[&[open]:not([data-size=full])]:[transition:none] " +
            "data-[part=content]:data-[dragging]:[&[open]:not([data-size=full])]:[translate:0_var(--uiify-sheet-offset,_0)] " +
            "data-[part=content]:data-[backdrop=blur]:backdrop:[backdrop-filter:blur(4px)] " +
            "data-[part=content]:data-[backdrop=none]:backdrop:bg-transparent " +
            "[&[data-part=content]_[data-uiify-modal][data-part=content]::backdrop]:bg-transparent " +
            "[&[data-part=content]_[data-uiify-modal][data-part=content]::backdrop]:[backdrop-filter:none] " +
            "data-[part=content]:[&:has([data-uiify-modal][data-part=content]:modal)]:[filter:brightness(0.9)] " +
            "[@media_(width_>=_40rem)]:data-[part=content]:[&:has([data-uiify-modal][data-part=content]:modal)]:[scale:0.97]",
        },
        grabber: {
          className:
            "hidden flex-none [block-size:1.5rem] " +
            "[margin-block:calc(-1_*_var(--space-md))_calc(-1_*_var(--space-sm))] " +
            "[touch-action:none] after:content-[''] after:block after:[inline-size:2.25rem] " +
            "after:[block-size:0.25rem] after:[margin-block:0.5rem_0] " +
            "after:[margin-inline:auto] after:[border-radius:999px] after:bg-border",
        },
        header: { className: "flex-none [padding-inline-end:var(--control-md)]" },
        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)]",
        },
        close: {
          className:
            "relative isolate inline-flex items-center justify-center gap-1.5 min-h-10 min-w-10 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)] px-0 absolute [inset-block-start:10px] [inset-inline-end:10px] grid place-items-center empty:before:[all:unset] empty:before:content-[''] empty:before:[grid-area:1/1] empty:before:[inline-size:0.875rem] empty:before:[block-size:2px] empty:before:[border-radius:1px] empty:before:bg-current empty:before:[rotate:45deg] empty:after:[all:unset] empty:after:content-[''] empty:after:[grid-area:1/1] empty:after:[inline-size:0.875rem] empty:after:[block-size:2px] empty:after:[border-radius:1px] empty:after:bg-current empty:after:[rotate:-45deg]",
        },
        body: { className: "flex-auto [min-block-size:0] overflow-auto overscroll-contain " + "[touch-action:pan-y]" },
        footer: { className: "flex flex-none flex-wrap justify-end gap-sm" },
      }}
    />
  );
}

API

Modal

ModalTrigger

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

ModalContent

מאפייןטיפוסברירת מחדלתיאור
id*string—מזהה ייחודי שמספק המשתמש בספרייה; כפתור הפתיחה ויעד הסגירה מפנים אליו.
size"sm" | "md" | "lg" | "full""md"מדרגת רוחב (24, 28, 40rem). הערך full ממלא את אזור התצוגה. נכתב ל-data-size; לרוחב מותאם אישית יש לדרוס את --uiify-modal-inline-size.
align"center" | "start""center"מיקום אנכי בטאבלט ובמחשב. בטלפונים מוצג לוח תחתון אלא אם size הוא full.
dismiss"any" | "closerequest" | "none""any"closedby: הערך any מוסיף לחיצות על הרקע, closerequest מאפשר רק Escape, ו-none רק את הפקדים שלכם. Safari אינו תומך ב-closedby, ולכן Escape תמיד סוגר; UIEnhance מטפל בלחיצות על הרקע (any) ובהחלקת הלוח (כשאינו none).
backdrop"opaque" | "blur" | "none""opaque"מראה הרקע. נכתב ל-data-backdrop.
childrenReactNode—תוכן תיבת הדו-שיח.

ModalClose

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

ModalHeader

מאפייןטיפוסברירת מחדלתיאור
childrenReactNode—שורה עליונה מקובעת המכילה את הכותרת, התיאור ופקד הסגירה.

ModalTitle

מאפייןטיפוסברירת מחדלתיאור
childrenReactNode—השם הנגיש של תיבת הדו-שיח; יש להפנות למזהה שלו מ-aria-labelledby.

ModalDescription

מאפייןטיפוסברירת מחדלתיאור
childrenReactNode—טקסט משלים; יש להפנות למזהה שלו מ-aria-describedby.

ModalBody

מאפייןטיפוסברירת מחדלתיאור
childrenReactNode—אזור הגלילה בין הכותרת העליונה לתחתית.

ModalFooter

מאפייןטיפוסברירת מחדלתיאור
childrenReactNode—שורת פעולות המקובעת מתחת לגוף.

ModalGrabber

מאפייןטיפוסברירת מחדלתיאור
childrenReactNode—ידית גרירה דקורטיבית בלוח הנייד; ההחלקה מתחילה כאן.

ModalClientContent (@gunkin/uiify/components/modal/client)

מאפייןטיפוסברירת מחדלתיאור
id*string—מזהה ייחודי שמספק המשתמש בספרייה; חלקי הפתיחה והסגירה מפנים אליו.
openboolean—מצב פתיחה מבוקר, הקובע לאחר ההידרציה.
defaultOpenboolean—מצב התחלתי לא מבוקר, המוחל באמצעות showModal() לאחר ההידרציה; ה-HTML מהשרת נשאר סגור.
onOpenChange(open: boolean) => void—מופעלת פעם אחת לכל שינוי מובנה או מתוכנת עם המצב החדש, לרבות פעם אחת בעת הטעינה אם הדפדפן פתח את התיבה לפני ההידרציה.
onOpenChangeComplete(open: boolean) => void—מופעלת פעם אחת לכל מעבר פתיחה או סגירה לאחר סיום מעברי ה-CSS שלו, או מיד אם אין מעברים, לרבות מעבר defaultOpen לאחר ההידרציה; לעולם לא כשתיבת הדו-שיח נשארת ללא שינוי.
size"sm" | "md" | "lg" | "full""md"מדרגת רוחב (24, 28, 40rem). הערך full ממלא את אזור התצוגה. נכתב ל-data-size; לרוחב מותאם אישית יש לדרוס את --uiify-modal-inline-size.
align"center" | "start""center"מיקום אנכי בטאבלט ובמחשב. בטלפונים מוצג לוח תחתון אלא אם size הוא full.
dismiss"any" | "closerequest" | "none""any"closedby: הערך any מוסיף לחיצות על הרקע, closerequest מאפשר רק Escape, ו-none רק את הפקדים שלכם. Safari אינו תומך ב-closedby, ולכן Escape תמיד סוגר; UIEnhance מטפל בלחיצות על הרקע (any) ובהחלקת הלוח (כשאינו none).
backdrop"opaque" | "blur" | "none""opaque"מראה הרקע. נכתב ל-data-backdrop.
childrenReactNode—תוכן תיבת הדו-שיח.

מקלדת

מקשפעולה
Escapeסגירה (רק המודאל העליון במקרה של קינון)
Tabמעבר מחזורי של המיקוד בתוך המודאל
Shift+Tabסדר Tab הפוך

נגישות

אלמנט <dialog> מובנה שנפתח באמצעות מפעיל עם command="show-modal" לפני הרצת JavaScript כלשהו. הדפדפן אחראי למודאליות, לכליאת המיקוד, ל-Escape ולהחזרת המיקוד; הסגירה משתמשת ב-request-close כך ש-onCancel יכול למנוע אותה. סמל הסגירה בכותרת תמיד קיים. בטלפונים המודאל הוא יריעה תחתונה; החלקה כלפי מטה והקשה על הרקע הן קיצורי דרך, ולעולם אינן הדרך היחידה לצאת.

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

data-uiify-modaldata-part="trigger|content|header|title|description|body|footer|grabber|close"data-size="sm|lg|full"data-align="start"data-backdrop="blur|none"--uiify-trigger-open (inside [data-uiify-modal][data-part="root"])--uiify-modal-inline-size (dialog width)
דף זה תורגם אוטומטית וטרם נבדק.