К содержимому
uiify

Hover card

Карточка предварительного просмотра, которая открывается при наведении.

Браузеры
Chrome 114+Firefox 125+Safari 17+
Уровень
1
Сжатый размер
1.92 kB
Лимит принудительной компоновки
0 ms
Лимит axe
0
Совместим с SSR
Да
Публичный контракт
Тестовый стенд

Песочница

Hover Card

Closed

UIify

Native-first React components with accessible semantics and optional styles.

Open-source · React 19 · Server compatible

Open

@uiify
UIify

Native-first React components with accessible semantics and optional styles.

Open-source · React 19 · Server compatible
import { HoverCard } from "@gunkin/uiify";

export default function Example() {
  return (
    <HoverCard.Root>
      <HoverCard.Trigger className="">@uiify</HoverCard.Trigger>
      <HoverCard.Content className="">Accessible React components that work before hydration.</HoverCard.Content>
    </HoverCard.Root>
  );
}
/* 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;
  }
}

/* Popup, popover, and tooltip surfaces. */

@layer components {
  :where(
    [data-uiify-popover][data-part="surface"],
    [data-uiify-popup][data-part="content"],
    [data-uiify-hover-card][data-part="content"]
  ):where(:not([popover]), :popover-open) {
    display: flex;
  }

  :where(
    [data-uiify-popover][data-part="surface"],
    [data-uiify-popup][data-part="content"],
    [data-uiify-hover-card][data-part="content"]
  ) {
    flex-direction: column;
    gap: var(--space-sm);
    inline-size: max-content;
    max-inline-size: 18rem;
    padding: var(--space-md);
    border: var(--border-width) solid var(--border);
    border-radius: var(--radius-lg);
    corner-shape: squircle;
    background-color: var(--popover);
    color: var(--popover-foreground);
    box-shadow: var(--shadow-popover);
  }

  :where(
    [data-uiify-popover][data-part="surface"],
    [data-uiify-popup][data-part="content"],
    [data-uiify-hover-card][data-part="content"]
  )[data-state="closed"] {
    display: none;
  }

  [data-uiify-tooltip]:where(:not([popover]), :popover-open) {
    display: block;
  }

  [data-uiify-tooltip] {
    inline-size: max-content;
    max-inline-size: 16rem;
    padding-block: var(--space-xs);
    padding-inline: var(--space-sm);

    /* Cancels the UA popover default (`border: solid`, currentColor) — this
       surface is borderless by design. */
    border: none;
    border-radius: var(--radius-sm);
    corner-shape: squircle;
    background-color: var(--foreground);
    color: var(--background);
    font-size: var(--text-xs);
    line-height: var(--leading-normal);
    box-shadow: var(--shadow-popover);
  }

  :where([data-uiify-tooltip])[data-state="closed"] {
    display: none;
  }

  @media (prefers-reduced-motion: no-preference) {
    :where(
      [data-uiify-popover][data-part="surface"],
      [data-uiify-popup][data-part="content"],
      [data-uiify-hover-card][data-part="content"],
      [data-uiify-tooltip]
    ) {
      transition: var(--transition-presence);
    }

    :where(
        [data-uiify-popover][data-part="surface"],
        [data-uiify-popup][data-part="content"],
        [data-uiify-hover-card][data-part="content"],
        [data-uiify-tooltip]
      )[popover]:not(:popover-open) {
      opacity: 0;
      scale: var(--presence-scale);
    }

    @starting-style {
      :where(
        [data-uiify-popover][data-part="surface"],
        [data-uiify-popup][data-part="content"],
        [data-uiify-hover-card][data-part="content"],
        [data-uiify-tooltip]
      ):popover-open {
        opacity: 0;
        scale: var(--presence-scale);
      }
    }
  }
}
import { HoverCard } from "@gunkin/uiify";

export default function Example() {
  return (
    <HoverCard.Root>
      <HoverCard.Trigger
        className={
          "focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--" +
          "ring)_16%,_transparent)] [--uiify-button-rim:rgb(255_255_255_/_20%)] [--uiify-button-wash:light-dark(rgb(" +
          "255_255_255_/_15%),_rgb(255_255_255_/_30%))] relative isolate inline-flex items-center justify-center [ga" +
          "p:0.375rem] [min-block-size:44px] [min-inline-size:44px] py-0 [padding-inline:1.25rem] border-[length:var" +
          "(--border-width)] border-solid border-transparent rounded-[var(--radius-full)] bg-foreground bg-clip-padd" +
          "ing text-background text-base font-[number:var(--weight-medium)] leading-6 no-underline cursor-pointer [t" +
          "ransition:var(--transition-colors)] before:content-[''] before:absolute before:inset-0 before:z-[-1] befo" +
          "re:[border-radius:inherit] before:pointer-events-none after:content-[''] after:absolute after:inset-0 aft" +
          "er:z-[-1] after:[border-radius:inherit] after:pointer-events-none before:[background-color:var(--uiify-bu" +
          "tton-wash)] before:[filter:blur(10px)] before:[translate:0_-50%] before:[clip-path:inset(50%_0_-50%_0_rou" +
          "nd_var(--radius-full))] after:[padding:0.5px] after:[background-image:linear-gradient(to_bottom,_var(--ui" +
          "ify-button-rim),_transparent_75%)] after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-grad" +
          "ient(#fff_0_0)] [&:is([data-size=sm],[data-size=icon-sm])]:[min-block-size:32px] [&:is([data-size=sm],[da" +
          "ta-size=icon-sm])]:[min-inline-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[padding-inline:0.75" +
          "rem] [&:is([data-size=sm],[data-size=icon-sm])]:[gap:0.25rem] [&:is([data-size=sm],[data-size=icon-sm])]:" +
          "text-sm [&:is([data-size=sm],[data-size=icon-sm])]:leading-5 [&:is([data-size=lg],[data-size=icon-lg])]:[" +
          "min-block-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[min-inline-size:56px] [&:is([data-size=l" +
          "g],[data-size=icon-lg])]:[padding-inline:1.75rem] [&:is([data-size=lg],[data-size=icon-lg])]:text-lg [&:i" +
          "s([data-size=lg],[data-size=icon-lg])]:leading-7 [&[data-size^=icon]]:px-0 [@media_(any-hover:_hover)_and" +
          "_(any-pointer:_fine)]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(" +
          "--foreground)_90%,_var(--background))] active:[&:not(:disabled,[data-disabled])]:[background-color:color-" +
          "mix(in_oklch,_var(--foreground)_80%,_var(--background))] disabled:opacity-50 disabled:cursor-not-allowed " +
          "data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[variant=secondary]:[&:where(:not(:che" +
          "cked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:light-dark(rgb(0_0_" +
          "0_/_5%),_rgb(255_255_255_/_10%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria" +
          "-pressed=true],[aria-current=page]))]:[--uiify-button-wash:light-dark(rgb(255_255_255_/_20%),_rgb(255_255" +
          "_255_/_5%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-" +
          "current=page]))]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] data-[variant" +
          "=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-fore" +
          "ground data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=" +
          "page]))]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreground)_25%,_transparent)] data-[variant=outli" +
          "ne]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[border-color:col" +
          "or-mix(in_oklch,_var(--foreground)_20%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[da" +
          "ta-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=outline]:[&:where(:n" +
          "ot(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=ou" +
          "tline]:before:content-none data-[variant=outline]:after:[inset:calc(-1_*_var(--border-width))] data-[vari" +
          "ant=outline]:after:[padding:var(--border-width)] data-[variant=outline]:[&:where(:not(:checked+*,[data-se" +
          "lected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-" +
          "color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=ghost]:[&:where(:not(:checke" +
          "d+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=ghost]:[&:whe" +
          "re(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[varia" +
          "nt=ghost]:before:content-none data-[variant=ghost]:after:content-none data-[variant=ghost]:[&:where(:not(" +
          ":checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disab" +
          "led])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=secondary" +
          "]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disa" +
          "bled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_26%,_transparent)] data-[v" +
          "ariant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[" +
          "--uiify-button-wash:rgb(255_255_255_/_15%)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-sel" +
          "ected],[aria-pressed=true],[aria-current=page]))]:bg-destructive data-[variant=destructive]:text-destruct" +
          "ive-foreground data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[a" +
          "ria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(" +
          "--destructive),_black_20%)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=outline]:[&" +
          ":where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled," +
          "[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any" +
          "-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[ar" +
          "ia-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-" +
          "mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:da" +
          "ta-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))" +
          "]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_t" +
          "ransparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=destructive]:[&:where(:not" +
          "(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disab" +
          "led])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_12%)] [&:is([data-selected],[aria-" +
          "pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[ar" +
          "ia-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-prim" +
          "ary-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-primar" +
          "y-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:n" +
          "ot(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground" +
          "))] [:disabled+&]:opacity-50 [:disabled+&]:cursor-not-allowed [&[hidden]]:hidden data-[variant=link]:px-0" +
          " data-[variant=link]:border-0 data-[variant=link]:border-current data-[variant=link]:border-none data-[va" +
          "riant=link]:rounded-none data-[variant=link]:bg-transparent data-[variant=link]:text-primary data-[varian" +
          "t=link]:underline data-[variant=link]:[text-underline-offset:0.2em] data-[variant=link]:active:px-0 data-" +
          "[variant=link]:active:border-0 data-[variant=link]:active:border-current data-[variant=link]:active:borde" +
          "r-none data-[variant=link]:active:rounded-none data-[variant=link]:active:bg-transparent data-[variant=li" +
          "nk]:active:text-primary data-[variant=link]:active:underline data-[variant=link]:active:[text-underline-o" +
          "ffset:0.2em] data-[variant=link]:[min-inline-size:0] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:" +
          "data-[variant=link]:hover:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:ho" +
          "ver:border-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-current" +
          " [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-none [@media_(any-h" +
          "over:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:rounded-none [@media_(any-hover:_hover)_a" +
          "nd_(any-pointer:_fine)]:data-[variant=link]:hover:bg-transparent [@media_(any-hover:_hover)_and_(any-poin" +
          "ter:_fine)]:data-[variant=link]:hover:text-primary [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:da" +
          "ta-[variant=link]:hover:underline [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]" +
          ":hover:[text-underline-offset:0.2em] data-[variant=link]:before:content-none data-[variant=link]:after:co" +
          "ntent-none data-[variant=link]:focus-visible:[outline:2px_solid_var(--ring)] data-[variant=link]:focus-vi" +
          "sible:[outline-offset:3px] [[data-uiify-toggle]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width" +
          ")_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-toggle-group]_[data-part=item]>input:fo" +
          "cus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[d" +
          "ata-uiify-modal][data-part=close]&]:border-transparent [[data-uiify-modal][data-part=close]&]:[background" +
          "-color:color-mix(in_oklch,_var(--foreground)_8%,_transparent)] [[data-uiify-modal][data-part=close]&]:tex" +
          "t-foreground [[data-uiify-modal][data-part=close]&:hover]:[background-color:color-mix(in_oklch,_var(--for" +
          "eground)_14%,_transparent)] [[data-uiify-modal][data-part=close]&:active]:[background-color:color-mix(in_" +
          "oklch,_var(--foreground)_20%,_transparent)] [[data-uiify-pagination]_span&:not([data-disabled])]:cursor-d" +
          "efault [[data-uiify-input-group]>[data-part=addon]>&]:[min-inline-size:0] [[data-uiify-input-group]>[data" +
          "-part=addon]>&]:[max-inline-size:100%] [[data-uiify-input-group]>[data-part=addon]>&]:whitespace-normal [" +
          "[data-uiify-input-group]>[data-part=addon]>&]:[overflow-wrap:anywhere] [[data-uiify-item-content]>&]:[jus" +
          "tify-self:start]"
        }
      >
        @uiify
      </HoverCard.Trigger>
      <HoverCard.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] flex-col gap-sm w-max max-w-72 p-md
          border-[length:var(--border-width)] border-solid border-border rounded-lg [corner-shape:squircle] bg-popover
          text-popover-foreground shadow-popover [&:where(:not([popover]),:popover-open)]:flex
          data-[state=closed]:hidden [&[data-positioning=native]]:[--uiify-anchor-offset:var(--space-sm)]
          motion-safe:[transition:var(--transition-presence)] motion-safe:[&:not(:popover-open)]:opacity-0
          motion-safe:[&:not(:popover-open)]:[scale:var(--presence-scale)]
          motion-safe:starting:[&:popover-open]:opacity-0
          motion-safe:starting:[&:popover-open]:[scale:var(--presence-scale)]
        "
      >
        Accessible React components that work before hydration.
      </HoverCard.Content>
    </HoverCard.Root>
  );
}
/* app.css */

@layer reset, ui, app, tokens, base, demo, components, utilities, theme;

@import "@gunkin/uiify/styles/reset";

@import "@gunkin/uiify/components/behavior.css";

@import "@gunkin/uiify/styles/tokens";

@import "@gunkin/uiify/styles/tailwind";

@import "tailwindcss/utilities.css";

@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }

@source inline('focus-visible:[outline:none] focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [--uiify-button-rim:rgb(255_255_255_/_20%)] [--uiify-button-wash:light-dark(rgb(255_255_255_/_15%),_rgb(255_255_255_/_30%))] relative isolate inline-flex items-center justify-center [gap:0.375rem] [min-block-size:44px] [min-inline-size:44px] py-0 [padding-inline:1.25rem] border-[length:var(--border-width)] border-solid border-transparent rounded-[var(--radius-full)] bg-foreground bg-clip-padding text-background text-base font-[number:var(--weight-medium)] leading-6 no-underline cursor-pointer [transition:var(--transition-colors)] before:absolute before:inset-0 before:z-[-1] before:[border-radius:inherit] before:pointer-events-none after:absolute after:inset-0 after:z-[-1] after:[border-radius:inherit] after:pointer-events-none before:[background-color:var(--uiify-button-wash)] before:[filter:blur(10px)] before:[translate:0_-50%] before:[clip-path:inset(50%_0_-50%_0_round_var(--radius-full))] after:[padding:0.5px] after:[background-image:linear-gradient(to_bottom,_var(--uiify-button-rim),_transparent_75%)] after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [&:is([data-size=sm],[data-size=icon-sm])]:[min-block-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[min-inline-size:32px] [&:is([data-size=sm],[data-size=icon-sm])]:[padding-inline:0.75rem] [&:is([data-size=sm],[data-size=icon-sm])]:[gap:0.25rem] [&:is([data-size=sm],[data-size=icon-sm])]:text-sm [&:is([data-size=sm],[data-size=icon-sm])]:leading-5 [&:is([data-size=lg],[data-size=icon-lg])]:[min-block-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[min-inline-size:56px] [&:is([data-size=lg],[data-size=icon-lg])]:[padding-inline:1.75rem] [&:is([data-size=lg],[data-size=icon-lg])]:text-lg [&:is([data-size=lg],[data-size=icon-lg])]:leading-7 [&[data-size^=icon]]:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_90%,_var(--background))] active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_80%,_var(--background))] disabled:opacity-50 disabled:cursor-not-allowed data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:light-dark(rgb(0_0_0_/_5%),_rgb(255_255_255_/_10%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-wash:light-dark(rgb(255_255_255_/_20%),_rgb(255_255_255_/_5%))] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-rim:color-mix(in_oklch,_var(--foreground)_25%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=outline]:before:content-none data-[variant=outline]:after:[inset:calc(-1_*_var(--border-width))] data-[variant=outline]:after:[padding:var(--border-width)] data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-transparent data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:text-foreground data-[variant=ghost]:before:content-none data-[variant=ghost]:after:content-none data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_26%,_transparent)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:[--uiify-button-wash:rgb(255_255_255_/_15%)] data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:bg-destructive data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:active:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_20%)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=outline]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=ghost]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=secondary]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=destructive]:[&:where(:not(:checked+*,[data-selected],[aria-pressed=true],[aria-current=page]))]:hover:[&:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--destructive),_black_12%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:border-transparent [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:bg-primary [&:is([data-selected],[aria-pressed=true],[aria-current=page])]:text-primary-foreground [:checked+&]:[--uiify-button-rim:rgb(255_255_255_/_20%)] [:checked+&]:[--uiify-button-wash:rgb(255_255_255_/_15%)] [:checked+&]:border-transparent [:checked+&]:bg-primary [:checked+&]:text-primary-foreground [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&:is([data-selected],[aria-pressed=true],[aria-current=page]):hover:not(:disabled,[data-disabled])]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[:checked:not(:disabled)+&:hover]:[background-color:color-mix(in_oklch,_var(--primary)_88%,_var(--primary-foreground))] [:disabled+&]:opacity-50 [:disabled+&]:cursor-not-allowed [&[hidden]]:hidden data-[variant=link]:px-0 data-[variant=link]:border-0 data-[variant=link]:border-current data-[variant=link]:border-none data-[variant=link]:rounded-none data-[variant=link]:bg-transparent data-[variant=link]:text-primary data-[variant=link]:underline data-[variant=link]:[text-underline-offset:0.2em] data-[variant=link]:active:px-0 data-[variant=link]:active:border-0 data-[variant=link]:active:border-current data-[variant=link]:active:border-none data-[variant=link]:active:rounded-none data-[variant=link]:active:bg-transparent data-[variant=link]:active:text-primary data-[variant=link]:active:underline data-[variant=link]:active:[text-underline-offset:0.2em] data-[variant=link]:[min-inline-size:0] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:px-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-0 [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-current [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:border-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:rounded-none [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:bg-transparent [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:text-primary [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:underline [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:data-[variant=link]:hover:[text-underline-offset:0.2em] data-[variant=link]:before:content-none data-[variant=link]:after:content-none data-[variant=link]:focus-visible:[outline:2px_solid_var(--ring)] data-[variant=link]:focus-visible:[outline-offset:3px] [[data-uiify-toggle]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-toggle-group]_[data-part=item]>input:focus-visible+&]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [[data-uiify-modal][data-part=close]&]:border-transparent [[data-uiify-modal][data-part=close]&]:[background-color:color-mix(in_oklch,_var(--foreground)_8%,_transparent)] [[data-uiify-modal][data-part=close]&]:text-foreground [[data-uiify-modal][data-part=close]&:hover]:[background-color:color-mix(in_oklch,_var(--foreground)_14%,_transparent)] [[data-uiify-modal][data-part=close]&:active]:[background-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [[data-uiify-pagination]_span&:not([data-disabled])]:cursor-default [[data-uiify-input-group]>[data-part=addon]>&]:[min-inline-size:0] [[data-uiify-input-group]>[data-part=addon]>&]:[max-inline-size:100%] [[data-uiify-input-group]>[data-part=addon]>&]:whitespace-normal [[data-uiify-input-group]>[data-part=addon]>&]:[overflow-wrap:anywhere] [[data-uiify-item-content]>&]:[justify-self:start] [&[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] flex-col gap-sm w-max max-w-72 p-md border-border rounded-lg [corner-shape:squircle] bg-popover text-popover-foreground shadow-popover [&:where(:not([popover]),:popover-open)]:flex data-[state=closed]:hidden motion-safe:[transition:var(--transition-presence)] motion-safe:[&:not(:popover-open)]:opacity-0 motion-safe:[&:not(:popover-open)]:[scale:var(--presence-scale)] motion-safe:starting:[&:popover-open]:opacity-0 motion-safe:starting:[&:popover-open]:[scale:var(--presence-scale)]');

@source inline("before:content-[''] after:content-['']");

@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }

/* Authored animation and registered properties used by the Tailwind skin.
 * Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
  @keyframes uiify-presence-in {
    from {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@layer components {
  @keyframes uiify-presence-out {
    to {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }
}

@layer components {
  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

@property --candle-body-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-body-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-slot {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}

@property --candle-wick-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-wick-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-x {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --line-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-prev-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-count {
  syntax: "<integer>";
  inherits: false;
  initial-value: 1;
}

@property --line-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@layer components {
  @keyframes uiify-otp-input-caret-blink {
    50% {
      opacity: 0;
    }
  }
}

API

HoverCard.Root

СвойствоТипПо умолчаниюОписание
openboolean—Управляемое состояние открытия.
defaultOpenbooleanfalseНеуправляемое начальное состояние открытия.
onOpenChangePopoverChangeHandler—Вызывается со следующим состоянием открытия и подробностями о причине изменения.
openDelaynumber700Время в миллисекундах, которое указатель должен оставаться на триггере до открытия карточки.
closeDelaynumber300Задержка закрытия карточки в миллисекундах после ухода указателя, позволяющая перевести его на карточку.
idstring—Базовый идентификатор якоря и содержимого; если не задан, создаётся автоматически.
childrenReactNode—Части Trigger и Content.

HoverCard.Trigger

СвойствоТипПо умолчаниюОписание
classNamestring—Класс элемента триггера.
childrenReactNode—Содержимое элемента триггера.
asElementType"button"Отображает другой элемент или компонент вместо тега по умолчанию.
renderReactElement | ((props, state) => ReactElement)—Заменяет отображаемый элемент, сохраняя поведение и связи доступности.

HoverCard.Content

СвойствоТипПо умолчаниюОписание
side"top" | "right" | "bottom" | "left""bottom"Сторона триггера, к которой привязана карточка.
align"start" | "center" | "end""center"Выравнивание карточки вдоль выбранной стороны.
classNamestring—Класс поверхности карточки.
childrenReactNode—Содержимое карточки.
asElementType"div"Отображает другой элемент или компонент вместо тега по умолчанию.
renderReactElement | ((props, state) => ReactElement)—Заменяет отображаемый элемент, сохраняя поведение и связи доступности.

Клавиатура

КлавишаДействие
TabПеревести фокус на элемент управления или с него
Enter / SpaceАктивировать при наличии фокуса (для элементов управления типа кнопки)

Доступность

По возможности используется семантический HTML. Изучите разметку в песочнице: ARIA добавляется только там, где нативных элементов недостаточно.

Привязка стилей

data-disabled