‮Skip to content‬
uiify

Carousel

‮Slide carousel with touch gestures and previous and next controls.‬

‮Browsers‬
Chrome 123+Firefox 121+Safari 17.5+
‮Tier‬
2
‮Bundle compressed‬
7.17 kB
‮Forced layout budget‬
0 ms
‮Axe budget‬
0
‮SSR compatible‬
‮Yes‬
‮Published contract‬
‮Test harness‬

‮Playground‬

Carousel

Featured cards

Move between stories with the arrows or select a slide with the dots.

Responsive card rail

Right to left

Block axis

Synchronized controls and native commands

Autoplay with Stop and Start

Focus stops rotation until Start is pressed. Hover pauses temporarily.

Optional mouse drag

Fade with CSS transitions

"use client";

import { CarouselClient } from "@gunkin/uiify/components/carousel/client";

const items = ["One", "Two", "Three"].map((label) => ({
  id: label,
  label,
  className: "",
  children: <div className="">{label}</div>,
}));

export default function Example() {
  return <CarouselClient id="featured" aria-label="Featured cards" className="" items={items} />;
}
/* Optional carousel skin. The native scrollbar is only hidden once real controls replace it. */
@layer components {
  [data-uiify-carousel] {
    position: relative;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: var(--space-sm);
    inline-size: 100%;
    max-inline-size: 40rem;
  }

  /* Full-width rows: an optional rotation button, the viewport, and the status text. */
  [data-uiify-carousel] > [data-part="viewport"],
  [data-uiify-carousel] > [data-part="status"],
  [data-uiify-carousel] > button[data-part="controls"][data-carousel-action="rotation"] {
    grid-column: 1 / -1;
  }

  /*
   * Hide the scrollbar only once the carousel is attached AND renders buttons that can
   * take over from it. Without scripting the scrollbar is the sole indication that the
   * content scrolls at all, so the static carousel keeps it. `scrollbar-width` alone is
   * enough for the supported engines — no prefixed fallback.
   */
  [data-uiify-carousel][data-enhanced]:has(> button[data-part="controls"])
    > [data-part="viewport"] {
    scrollbar-width: none;
  }

  [data-uiify-carousel]
    > [data-part="viewport"]
    > [data-part="slide"]
    > [data-part="slide-content"] {
    display: flex;
    container-type: inline-size;
    min-block-size: 18rem;
    aspect-ratio: 1;
    align-items: center;
    justify-content: center;
    border: var(--border-width) solid var(--border);
    border-radius: var(--radius-xl);
    corner-shape: squircle;
    background-color: var(--card);
    color: var(--foreground);
    font-size: 2.25rem;
    font-weight: var(--weight-semibold);
    line-height: 1;
    box-shadow: var(--shadow-popover);
  }

  /* The controls are Button-skinned (`outline`; previous/next at the icon size). */
  [data-uiify-carousel] > button[data-part="controls"][data-carousel-action="rotation"] {
    justify-self: start;
    margin-block-end: var(--space-sm);
  }

  [data-uiify-carousel] > button[data-part="controls"][data-carousel-action="previous"],
  [data-uiify-carousel] > button[data-part="controls"][data-carousel-action="next"] {
    font-size: 1.25rem;
    line-height: 1;
  }

  [data-uiify-carousel] > [data-part="dots"] > a:focus-visible {
    outline: none;
    box-shadow: 0 0 0 var(--ring-width) color-mix(in oklch, var(--ring) 16%, transparent);
  }

  [data-uiify-carousel] > [data-part="dots"] {
    /* Explicit middle column: pagination stays centred whether or not the
       previous/next buttons exist to occupy the outer columns. */
    grid-column: 2;
    justify-self: stretch;
    display: flex;
    justify-content: center;
    gap: var(--space-xs);
  }

  [data-uiify-carousel] > [data-part="dots"] > a {
    display: inline-grid;
    place-items: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border-radius: var(--radius-full);
    background-color: transparent;
    color: var(--muted-foreground);
    text-decoration: none;
    transition: var(--transition-colors);
    font-size: 0;
  }
  [data-uiify-carousel] > [data-part="dots"] > a::after {
    content: "";
    inline-size: 0.5rem;
    block-size: 0.5rem;
    border-radius: 50%;
    background: var(--input);
  }
  [data-uiify-carousel] > [data-part="dots"] > a[aria-current="true"]::after {
    background: var(--primary);
  }
}

@layer components {
  [data-uiify-carousel] [data-part="story"] {
    display: grid;
    grid-template-rows: auto 1fr auto;
    inline-size: 100%;
    block-size: 100%;
    min-inline-size: 0;
    min-block-size: 0;
    padding: clamp(0.875rem, 4cqi, 1.5rem);
    text-align: start;
  }

  [data-uiify-carousel] [data-part="story-topline"],
  [data-uiify-carousel] [data-part="story-footer"] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem;
    color: var(--muted-foreground);
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.08em;
  }

  [data-uiify-carousel] [data-part="story-body"] {
    display: grid;
    align-content: center;
    gap: 0.75rem;
    min-inline-size: 0;
    padding-block: 1rem;
  }

  [data-uiify-carousel] [data-part="story-eyebrow"] {
    color: var(--primary);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.1em;
  }

  [data-uiify-carousel] [data-part="story-body"] > h3 {
    max-inline-size: 12ch;
    margin: 0;
    color: var(--foreground);
    font-size: clamp(1.25rem, 6cqi, 2rem);
    font-weight: 600;
    letter-spacing: -0.045em;
    line-height: 1.08;
    white-space: pre-line;
  }

  [data-uiify-carousel] [data-part="story-body"] > p {
    max-inline-size: 30ch;
    margin: 0;
    color: var(--muted-foreground);
    font-size: 0.875rem;
    line-height: 1.55;
  }
}
"use client";

import { CarouselClient } from "@gunkin/uiify/components/carousel/client";

const items = ["One", "Two", "Three"].map((label) => ({
  id: label,
  label,
  className: "",
  children: <div className="">{label}</div>,
}));

export default function Example() {
  return (
    <CarouselClient
      id="featured"
      aria-label="Featured cards"
      className={
        "relative grid [grid-template-columns:auto_1fr_auto] items-center gap-sm [inline-size:100%] [max-inline-size" +
        ":40rem] [&>button[data-part=controls]]:relative [&>button[data-part=controls]]:isolate [&>button[data-part=" +
        "controls]]:inline-flex [&>button[data-part=controls]]:items-center [&>button[data-part=controls]]:justify-c" +
        "enter [&>button[data-part=controls][hidden]]:hidden [&>button[data-part=controls]]:min-h-11 [&>button[data-" +
        "part=controls]]:min-w-11 [&>button[data-part=controls]]:gap-1.5 [&>button[data-part=controls]]:px-5 [&>butt" +
        "on[data-part=controls]]:py-0 [&>button[data-part=controls]]:border-[length:var(--border-width)] [&>button[d" +
        "ata-part=controls]]:border-solid [&>button[data-part=controls]]:[border-color:color-mix(in_oklch,_var(--for" +
        "eground)_20%,_transparent)] [&>button[data-part=controls]]:rounded-full [&>button[data-part=controls]]:bg-t" +
        "ransparent [&>button[data-part=controls]]:bg-clip-padding [&>button[data-part=controls]]:text-foreground [&" +
        ">button[data-part=controls]]:font-medium [&>button[data-part=controls]]:text-base [&>button[data-part=contr" +
        "ols]]:leading-6 [&>button[data-part=controls]]:[font-family:inherit] [&>button[data-part=controls]]:no-unde" +
        "rline [&>button[data-part=controls]]:cursor-pointer [&>button[data-part=controls]]:[transition:var(--transi" +
        "tion-colors)] [&>button[data-part=controls]]:disabled:opacity-50 [&>button[data-part=controls]]:disabled:cu" +
        "rsor-not-allowed [&>button[data-part=controls]]:focus-visible:outline-none [&>button[data-part=controls]]:f" +
        "ocus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [@media" +
        "_(any-hover:_hover)_and_(any-pointer:_fine)]:[&>button[data-part=controls]:hover:not(:disabled)]:[backgroun" +
        "d-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [&>button[data-part=controls]:active:not(:" +
        "disabled)]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] [&>button[data-part=c" +
        "ontrols]]:before:content-none [&>button[data-part=controls]]:after:content-[''] [&>button[data-part=control" +
        "s]]:after:absolute [&>button[data-part=controls]]:after:[inset:calc(-1_*_var(--border-width))] [&>button[da" +
        "ta-part=controls]]:after:z-[-1] [&>button[data-part=controls]]:after:rounded-[inherit] [&>button[data-part=" +
        "controls]]:after:pointer-events-none [&>button[data-part=controls]]:after:[padding:var(--border-width)] [&>" +
        "button[data-part=controls]]:after:[background-image:linear-gradient(to_bottom,_color-mix(in_oklch,_var(--fo" +
        "reground)_25%,_transparent),_transparent_75%)] [&>button[data-part=controls]]:after:[mask:linear-gradient(#" +
        "fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [&>[data-part=viewport]]:[grid-column:1_/_-1] [&>[" +
        "data-part=status]]:[grid-column:1_/_-1] [&>button[data-part=controls][data-carousel-action=rotation]]:[grid" +
        "-column:1_/_-1] data-[enhanced]:[&:has(>button[data-part=controls])>[data-part=viewport]]:[scrollbar-width:" +
        "none] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:flex [&>[data-part=viewport]>[da" +
        "ta-part=slide]>[data-part=slide-content]]:[container-type:inline-size] [&>[data-part=viewport]>[data-part=s" +
        "lide]>[data-part=slide-content]]:[min-block-size:18rem] [&>[data-part=viewport]>[data-part=slide]>[data-par" +
        "t=slide-content]]:[aspect-ratio:1] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:ite" +
        "ms-center [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:justify-center [&>[data-part" +
        "=viewport]>[data-part=slide]>[data-part=slide-content]]:border-[length:var(--border-width)] [&>[data-part=v" +
        "iewport]>[data-part=slide]>[data-part=slide-content]]:border-solid [&>[data-part=viewport]>[data-part=slide" +
        "]>[data-part=slide-content]]:border-border [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-conte" +
        "nt]]:rounded-xl [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:[corner-shape:squircle" +
        "] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:bg-card [&>[data-part=viewport]>[dat" +
        "a-part=slide]>[data-part=slide-content]]:text-foreground [&>[data-part=viewport]>[data-part=slide]>[data-pa" +
        "rt=slide-content]]:[font-size:2.25rem] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]" +
        ":font-[number:var(--weight-semibold)] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:" +
        "leading-none [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:shadow-popover [&>button[" +
        "data-part=controls][data-carousel-action=rotation]]:[justify-self:start] [&>button[data-part=controls][data" +
        "-carousel-action=rotation]]:[margin-block-end:var(--space-sm)] [&>button[data-part=controls][data-carousel-" +
        "action=previous]]:[font-size:1.25rem] [&>button[data-part=controls][data-carousel-action=previous]]:leading" +
        "-none [&>button[data-part=controls][data-carousel-action=next]]:[font-size:1.25rem] [&>button[data-part=con" +
        "trols][data-carousel-action=next]]:leading-none [&>[data-part=dots]>a:focus-visible]:[outline:none] [&>[dat" +
        "a-part=dots]>a:focus-visible]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_tran" +
        "sparent)] [&>[data-part=dots]]:[grid-column:2] [&>[data-part=dots]]:[justify-self:stretch] [&>[data-part=do" +
        "ts]]:flex [&>[data-part=dots]]:justify-center [&>[data-part=dots]]:gap-xs [&>[data-part=dots]>a]:inline-gri" +
        "d [&>[data-part=dots]>a]:place-items-center [&>[data-part=dots]>a]:[inline-size:2.75rem] [&>[data-part=dots" +
        "]>a]:[block-size:2.75rem] [&>[data-part=dots]>a]:rounded-[var(--radius-full)] [&>[data-part=dots]>a]:bg-tra" +
        "nsparent [&>[data-part=dots]>a]:text-muted-foreground [&>[data-part=dots]>a]:no-underline [&>[data-part=dot" +
        "s]>a]:[transition:var(--transition-colors)] [&>[data-part=dots]>a]:[font-size:0] [&>[data-part=dots]>a::aft" +
        "er]:content-[''] [&>[data-part=dots]>a::after]:[inline-size:0.5rem] [&>[data-part=dots]>a::after]:[block-si" +
        "ze:0.5rem] [&>[data-part=dots]>a::after]:rounded-[50%] [&>[data-part=dots]>a::after]:bg-input [&>[data-part" +
        "=dots]>a[aria-current=true]::after]:bg-primary [&_[data-part=story]]:grid [&_[data-part=story]]:[grid-templ" +
        "ate-rows:auto_1fr_auto] [&_[data-part=story]]:[inline-size:100%] [&_[data-part=story]]:[block-size:100%] [&" +
        "_[data-part=story]]:[min-inline-size:0] [&_[data-part=story]]:[min-block-size:0] [&_[data-part=story]]:[pad" +
        "ding:clamp(0.875rem,_4cqi,_1.5rem)] [&_[data-part=story]]:text-start [&_[data-part=story-topline]]:flex [&_" +
        "[data-part=story-topline]]:items-center [&_[data-part=story-topline]]:justify-between [&_[data-part=story-t" +
        "opline]]:flex-wrap [&_[data-part=story-topline]]:[gap:0.5rem] [&_[data-part=story-topline]]:text-muted-fore" +
        "ground [&_[data-part=story-topline]]:[font-family:ui-monospace,_\"SF_Mono\",_Menlo,_Consolas,_monospace] [&_[" +
        "data-part=story-topline]]:[font-size:0.75rem] [&_[data-part=story-topline]]:[font-weight:500] [&_[data-part" +
        "=story-topline]]:[letter-spacing:0.08em] [&_[data-part=story-footer]]:flex [&_[data-part=story-footer]]:ite" +
        "ms-center [&_[data-part=story-footer]]:justify-between [&_[data-part=story-footer]]:flex-wrap [&_[data-part" +
        "=story-footer]]:[gap:0.5rem] [&_[data-part=story-footer]]:text-muted-foreground [&_[data-part=story-footer]" +
        "]:[font-family:ui-monospace,_\"SF_Mono\",_Menlo,_Consolas,_monospace] [&_[data-part=story-footer]]:[font-size" +
        ":0.75rem] [&_[data-part=story-footer]]:[font-weight:500] [&_[data-part=story-footer]]:[letter-spacing:0.08e" +
        "m] [&_[data-part=story-body]]:grid [&_[data-part=story-body]]:[align-content:center] [&_[data-part=story-bo" +
        "dy]]:[gap:0.75rem] [&_[data-part=story-body]]:[min-inline-size:0] [&_[data-part=story-body]]:[padding-block" +
        ":1rem] [&_[data-part=story-eyebrow]]:text-primary [&_[data-part=story-eyebrow]]:[font-size:0.75rem] [&_[dat" +
        "a-part=story-eyebrow]]:[font-weight:600] [&_[data-part=story-eyebrow]]:[letter-spacing:0.1em] [&_[data-part" +
        "=story-body]>h3]:[max-inline-size:12ch] [&_[data-part=story-body]>h3]:m-0 [&_[data-part=story-body]>h3]:tex" +
        "t-foreground [&_[data-part=story-body]>h3]:[font-size:clamp(1.25rem,_6cqi,_2rem)] [&_[data-part=story-body]" +
        ">h3]:[font-weight:600] [&_[data-part=story-body]>h3]:[letter-spacing:-0.045em] [&_[data-part=story-body]>h3" +
        "]:[line-height:1.08] [&_[data-part=story-body]>h3]:[white-space:pre-line] [&_[data-part=story-body]>p]:[max" +
        "-inline-size:30ch] [&_[data-part=story-body]>p]:m-0 [&_[data-part=story-body]>p]:text-muted-foreground [&_[" +
        "data-part=story-body]>p]:[font-size:0.875rem] [&_[data-part=story-body]>p]:[line-height:1.55]"
      }
      items={items}
    />
  );
}
/* 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('relative grid [grid-template-columns:auto_1fr_auto] items-center gap-sm [inline-size:100%] [max-inline-size:40rem] [&>button[data-part=controls]]:relative [&>button[data-part=controls]]:isolate [&>button[data-part=controls]]:inline-flex [&>button[data-part=controls]]:items-center [&>button[data-part=controls]]:justify-center [&>button[data-part=controls][hidden]]:hidden [&>button[data-part=controls]]:min-h-11 [&>button[data-part=controls]]:min-w-11 [&>button[data-part=controls]]:gap-1.5 [&>button[data-part=controls]]:px-5 [&>button[data-part=controls]]:py-0 [&>button[data-part=controls]]:border-[length:var(--border-width)] [&>button[data-part=controls]]:border-solid [&>button[data-part=controls]]:[border-color:color-mix(in_oklch,_var(--foreground)_20%,_transparent)] [&>button[data-part=controls]]:rounded-full [&>button[data-part=controls]]:bg-transparent [&>button[data-part=controls]]:bg-clip-padding [&>button[data-part=controls]]:text-foreground [&>button[data-part=controls]]:font-medium [&>button[data-part=controls]]:text-base [&>button[data-part=controls]]:leading-6 [&>button[data-part=controls]]:[font-family:inherit] [&>button[data-part=controls]]:no-underline [&>button[data-part=controls]]:cursor-pointer [&>button[data-part=controls]]:[transition:var(--transition-colors)] [&>button[data-part=controls]]:disabled:opacity-50 [&>button[data-part=controls]]:disabled:cursor-not-allowed [&>button[data-part=controls]]:focus-visible:outline-none [&>button[data-part=controls]]:focus-visible:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [@media_(any-hover:_hover)_and_(any-pointer:_fine)]:[&>button[data-part=controls]:hover:not(:disabled)]:[background-color:color-mix(in_oklch,_var(--foreground)_10%,_transparent)] [&>button[data-part=controls]:active:not(:disabled)]:[background-color:color-mix(in_oklch,_var(--foreground)_16%,_transparent)] [&>button[data-part=controls]]:before:content-none [&>button[data-part=controls]]:after:absolute [&>button[data-part=controls]]:after:[inset:calc(-1_*_var(--border-width))] [&>button[data-part=controls]]:after:z-[-1] [&>button[data-part=controls]]:after:rounded-[inherit] [&>button[data-part=controls]]:after:pointer-events-none [&>button[data-part=controls]]:after:[padding:var(--border-width)] [&>button[data-part=controls]]:after:[background-image:linear-gradient(to_bottom,_color-mix(in_oklch,_var(--foreground)_25%,_transparent),_transparent_75%)] [&>button[data-part=controls]]:after:[mask:linear-gradient(#fff_0_0)_content-box_exclude,_linear-gradient(#fff_0_0)] [&>[data-part=viewport]]:[grid-column:1_/_-1] [&>[data-part=status]]:[grid-column:1_/_-1] [&>button[data-part=controls][data-carousel-action=rotation]]:[grid-column:1_/_-1] data-[enhanced]:[&:has(>button[data-part=controls])>[data-part=viewport]]:[scrollbar-width:none] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:flex [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:[container-type:inline-size] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:[min-block-size:18rem] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:[aspect-ratio:1] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:items-center [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:justify-center [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:border-[length:var(--border-width)] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:border-solid [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:border-border [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:rounded-xl [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:[corner-shape:squircle] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:bg-card [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:text-foreground [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:[font-size:2.25rem] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:font-[number:var(--weight-semibold)] [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:leading-none [&>[data-part=viewport]>[data-part=slide]>[data-part=slide-content]]:shadow-popover [&>button[data-part=controls][data-carousel-action=rotation]]:[justify-self:start] [&>button[data-part=controls][data-carousel-action=rotation]]:[margin-block-end:var(--space-sm)] [&>button[data-part=controls][data-carousel-action=previous]]:[font-size:1.25rem] [&>button[data-part=controls][data-carousel-action=previous]]:leading-none [&>button[data-part=controls][data-carousel-action=next]]:[font-size:1.25rem] [&>button[data-part=controls][data-carousel-action=next]]:leading-none [&>[data-part=dots]>a:focus-visible]:[outline:none] [&>[data-part=dots]>a:focus-visible]:[box-shadow:0_0_0_var(--ring-width)_color-mix(in_oklch,_var(--ring)_16%,_transparent)] [&>[data-part=dots]]:[grid-column:2] [&>[data-part=dots]]:[justify-self:stretch] [&>[data-part=dots]]:flex [&>[data-part=dots]]:justify-center [&>[data-part=dots]]:gap-xs [&>[data-part=dots]>a]:inline-grid [&>[data-part=dots]>a]:place-items-center [&>[data-part=dots]>a]:[inline-size:2.75rem] [&>[data-part=dots]>a]:[block-size:2.75rem] [&>[data-part=dots]>a]:rounded-[var(--radius-full)] [&>[data-part=dots]>a]:bg-transparent [&>[data-part=dots]>a]:text-muted-foreground [&>[data-part=dots]>a]:no-underline [&>[data-part=dots]>a]:[transition:var(--transition-colors)] [&>[data-part=dots]>a]:[font-size:0] [&>[data-part=dots]>a::after]:[inline-size:0.5rem] [&>[data-part=dots]>a::after]:[block-size:0.5rem] [&>[data-part=dots]>a::after]:rounded-[50%] [&>[data-part=dots]>a::after]:bg-input [&>[data-part=dots]>a[aria-current=true]::after]:bg-primary [&_[data-part=story]]:grid [&_[data-part=story]]:[grid-template-rows:auto_1fr_auto] [&_[data-part=story]]:[inline-size:100%] [&_[data-part=story]]:[block-size:100%] [&_[data-part=story]]:[min-inline-size:0] [&_[data-part=story]]:[min-block-size:0] [&_[data-part=story]]:[padding:clamp(0.875rem,_4cqi,_1.5rem)] [&_[data-part=story]]:text-start [&_[data-part=story-topline]]:flex [&_[data-part=story-topline]]:items-center [&_[data-part=story-topline]]:justify-between [&_[data-part=story-topline]]:flex-wrap [&_[data-part=story-topline]]:[gap:0.5rem] [&_[data-part=story-topline]]:text-muted-foreground [&_[data-part=story-topline]]:[font-family:ui-monospace,_"SF_Mono",_Menlo,_Consolas,_monospace] [&_[data-part=story-topline]]:[font-size:0.75rem] [&_[data-part=story-topline]]:[font-weight:500] [&_[data-part=story-topline]]:[letter-spacing:0.08em] [&_[data-part=story-footer]]:flex [&_[data-part=story-footer]]:items-center [&_[data-part=story-footer]]:justify-between [&_[data-part=story-footer]]:flex-wrap [&_[data-part=story-footer]]:[gap:0.5rem] [&_[data-part=story-footer]]:text-muted-foreground [&_[data-part=story-footer]]:[font-family:ui-monospace,_"SF_Mono",_Menlo,_Consolas,_monospace] [&_[data-part=story-footer]]:[font-size:0.75rem] [&_[data-part=story-footer]]:[font-weight:500] [&_[data-part=story-footer]]:[letter-spacing:0.08em] [&_[data-part=story-body]]:grid [&_[data-part=story-body]]:[align-content:center] [&_[data-part=story-body]]:[gap:0.75rem] [&_[data-part=story-body]]:[min-inline-size:0] [&_[data-part=story-body]]:[padding-block:1rem] [&_[data-part=story-eyebrow]]:text-primary [&_[data-part=story-eyebrow]]:[font-size:0.75rem] [&_[data-part=story-eyebrow]]:[font-weight:600] [&_[data-part=story-eyebrow]]:[letter-spacing:0.1em] [&_[data-part=story-body]>h3]:[max-inline-size:12ch] [&_[data-part=story-body]>h3]:m-0 [&_[data-part=story-body]>h3]:text-foreground [&_[data-part=story-body]>h3]:[font-size:clamp(1.25rem,_6cqi,_2rem)] [&_[data-part=story-body]>h3]:[font-weight:600] [&_[data-part=story-body]>h3]:[letter-spacing:-0.045em] [&_[data-part=story-body]>h3]:[line-height:1.08] [&_[data-part=story-body]>h3]:[white-space:pre-line] [&_[data-part=story-body]>p]:[max-inline-size:30ch] [&_[data-part=story-body]>p]:m-0 [&_[data-part=story-body]>p]:text-muted-foreground [&_[data-part=story-body]>p]:[font-size:0.875rem] [&_[data-part=story-body]>p]:[line-height:1.55]');

@source inline("[&>button[data-part=controls]]:after:content-[''] [&>[data-part=dots]>a::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‬

Carousel

CarouselClient — @gunkin/uiify/components/carousel/client

‮Prop‬‮Type‬‮Default‬‮Description‬
defaultValue / valuenumber—‮Initial or controlled item index; observed settled state drives controls.‬
rewindbooleanfalse‮Wrap finite ends without cloned slides.‬
mouseDragbooleanfalse‮Opt-in mouse drag. Touch and wheel remain native.‬
autoplayboolean | CarouselAutoplayOptionsfalse‮Delay defaults to 5000ms. Options: delay, paused, cycles and stopOnInteraction. Includes visible Stop/Start.‬
onSelect / beforeChange / afterChange / onSwipecallbacks—‮Report actual settled changes and reasons; see the Carousel guide for controlled and interrupted-motion semantics.‬
refRef<CarouselClientHandle>—‮next, previous, goTo, start, stop and reset.‬

FadeCarouselClient — @gunkin/uiify/components/carousel/fade

‮Prop‬‮Type‬‮Default‬‮Description‬
swipebooleantrue‮Touch/pen swipe; mouse requires mouseDrag. Inherits client props except axis. Inactive slides are inert.‬

‮Keyboard‬

‮Key‬‮Action‬
Tab / Enter‮Focus and follow native slide links; enhanced buttons activate with Space too‬
Arrow keys‮Use native scrolling while the viewport is focused‬

‮Accessibility‬

‮The base Carousel is a server component with native scrolling, snapping and finite fragment navigation. The interactive /client entry is Tier 2: synchronized dots, persistent arrows, callbacks, autoplay and mouse drag require hydration; /fade adds CSS opacity transitions. Focus stops autoplay until Start, hover pauses temporarily, and reduced motion disables automatic rotation by default. The CSS-generated-controls lab is experimental.‬

‮Styling hooks‬

data-uiify-carouseldata-axisdata-enhanceddata-modedata-carousel-indexdata-statedata-dragging--uiify-carousel-slide-size--uiify-carousel-gap--uiify-carousel-fade-duration