{
  "$schema": "https://mian.ro/schema/v2/registry.json",
  "schemaVersion": "2.0.0",
  "generatedAt": "2026-10-02T13:14:06.957Z",
  "id": "carousel",
  "name": "Carousel",
  "category": "Layout & utilities",
  "description": "A horizontally scrolling, snapping track. Swipe, trackpad and keyboard; optional arrows and dots.",
  "tags": [
    "carousel",
    "slider",
    "gallery",
    "slideshow",
    "swipe"
  ],
  "requiresJs": true,
  "license": "LicenseRef-MIAN-1.0",
  "composition": {
    "role": null,
    "intents": [
      "carousel",
      "slider of cards",
      "horizontal scroll",
      "image slider"
    ],
    "prominence": null,
    "density": null,
    "worksAfter": [],
    "worksBefore": [],
    "alternatives": [],
    "avoid": "As the only way to reach content, carousels hide things."
  },
  "url": "https://mian.ro/components/carousel",
  "markdownUrl": "https://mian.ro/ai/components/carousel.md",
  "classes": [
    {
      "name": "mn-carousel",
      "description": "Wrapper."
    },
    {
      "name": "mn-carousel-track · mn-carousel-item",
      "description": "Scroll-snap track and slides."
    },
    {
      "name": "mn-carousel-prev · mn-carousel-next · mn-carousel-dots",
      "description": "Controls; dots are generated if empty."
    },
    {
      "name": "mn-carousel-peek · mn-carousel-multi",
      "description": "Show a peek of the next slide · three per view."
    }
  ],
  "accessibility": [
    "The track is a focusable region; arrow keys move one slide. Each slide is labelled \"n of N\". Nothing auto-advances."
  ],
  "examples": [
    {
      "title": "With controls",
      "description": null,
      "html": "<div class=\"mn-carousel mn-carousel-peek\" aria-label=\"Featured noodles\" style=\"max-width:34rem\">\n  <div class=\"mn-carousel-track\">\n    <div class=\"mn-carousel-item\"><div class=\"mn-card mn-card-flat\" style=\"height:10rem;display:grid;place-items:center;background:color-mix(in oklab, var(--mn-primary) 10%, var(--mn-surface))\"><span class=\"mn-h3\">Udon</span></div></div>\n    <div class=\"mn-carousel-item\"><div class=\"mn-card mn-card-flat\" style=\"height:10rem;display:grid;place-items:center;background:color-mix(in oklab, var(--mn-primary) 18%, var(--mn-surface))\"><span class=\"mn-h3\">Soba</span></div></div>\n    <div class=\"mn-carousel-item\"><div class=\"mn-card mn-card-flat\" style=\"height:10rem;display:grid;place-items:center;background:color-mix(in oklab, var(--mn-primary) 26%, var(--mn-surface))\"><span class=\"mn-h3\">Ramen</span></div></div>\n    <div class=\"mn-carousel-item\"><div class=\"mn-card mn-card-flat\" style=\"height:10rem;display:grid;place-items:center;background:color-mix(in oklab, var(--mn-primary) 34%, var(--mn-surface))\"><span class=\"mn-h3\">Pho</span></div></div>\n    <div class=\"mn-carousel-item\"><div class=\"mn-card mn-card-flat\" style=\"height:10rem;display:grid;place-items:center;background:color-mix(in oklab, var(--mn-primary) 42%, var(--mn-surface))\"><span class=\"mn-h3\">Mee</span></div></div>\n  </div>\n  <button class=\"mn-carousel-prev\" aria-label=\"Previous slide\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m15 6-6 6 6 6\"/></svg></button>\n  <button class=\"mn-carousel-next\" aria-label=\"Next slide\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 6 6 6-6 6\"/></svg></button>\n  <div class=\"mn-carousel-dots\"></div>\n</div>"
    }
  ],
  "css": ".mn-carousel { position: relative; }\n.mn-carousel-track {\n  display: flex; gap: 1rem; overflow-x: auto; scroll-snap-type: x mandatory;\n  scroll-behavior: smooth; scrollbar-width: none; overscroll-behavior-x: contain;\n  border-radius: var(--mn-radius-box);\n}\n.mn-carousel-track::-webkit-scrollbar { display: none; }\n.mn-carousel-track:focus-visible { outline: none; box-shadow: var(--mn-focus); }\n.mn-carousel-item { flex: 0 0 100%; scroll-snap-align: start; min-width: 0; }\n.mn-carousel-peek .mn-carousel-item { flex-basis: 82%; }\n.mn-carousel-multi .mn-carousel-item { flex-basis: calc((100% - 2rem) / 3); }\n@media (max-width: 40rem) { .mn-carousel-multi .mn-carousel-item { flex-basis: 82%; } }\n.mn-carousel-prev, .mn-carousel-next {\n  position: absolute; top: 50%; translate: 0 -50%; z-index: 1;\n  display: grid; place-items: center; width: 2.25rem; height: 2.25rem;\n  border: var(--mn-border-w) solid var(--mn-border); border-radius: 50%;\n  background: var(--mn-popover); color: var(--mn-fg); box-shadow: var(--mn-shadow-md); cursor: pointer;\n}\n.mn-carousel-prev { left: .6rem; } .mn-carousel-next { right: .6rem; }\n.mn-carousel-prev:disabled, .mn-carousel-next:disabled { opacity: 0; pointer-events: none; }\n.mn-carousel-prev:focus-visible, .mn-carousel-next:focus-visible { outline: none; box-shadow: var(--mn-focus); }\n.mn-carousel-prev svg, .mn-carousel-next svg { width: 1rem; height: 1rem; }\n.mn-carousel-dots { display: flex; justify-content: center; gap: .4rem; margin-top: .75rem; }\n.mn-carousel-dots button { width: .5rem; height: .5rem; padding: 0; border: 0; border-radius: 999px; background: var(--mn-input); cursor: pointer; transition: width var(--mn-duration) var(--mn-ease), background-color var(--mn-duration) var(--mn-ease); }\n.mn-carousel-dots button[aria-current=\"true\"] { width: 1.25rem; background: var(--mn-fg); }\n.mn-carousel-dots button:focus-visible { outline: none; box-shadow: var(--mn-focus); }\n@media (prefers-reduced-motion: reduce) { .mn-carousel-track { scroll-behavior: auto; } }",
  "js": "register('carousel', (root) => {\n  each(root, '.mn-carousel', 'Car', (c) => {\n    const track = c.querySelector('.mn-carousel-track');\n    if (!track) return;\n    const items = [...track.querySelectorAll('.mn-carousel-item')];\n    const prev = c.querySelector('.mn-carousel-prev'), next = c.querySelector('.mn-carousel-next');\n    const dots = c.querySelector('.mn-carousel-dots');\n    if (!track.hasAttribute('tabindex')) track.tabIndex = 0;\n    track.setAttribute('role', 'region');\n    if (!track.getAttribute('aria-label')) track.setAttribute('aria-label', c.getAttribute('aria-label') || 'Carousel');\n    items.forEach((it, i) => { it.setAttribute('role', 'group'); it.setAttribute('aria-roledescription', 'slide'); it.setAttribute('aria-label', `${i + 1} of ${items.length}`); });\n    if (dots && !dots.children.length) {\n      dots.innerHTML = items.map((_, i) => `<button type=\"button\" aria-label=\"Go to slide ${i + 1}\"></button>`).join('');\n    }\n    const current = () => {\n      const x = track.scrollLeft;\n      let best = 0, bd = Infinity;\n      items.forEach((it, i) => { const d = Math.abs(it.offsetLeft - track.offsetLeft - x); if (d < bd) { bd = d; best = i; } });\n      return best;\n    };\n    const go = (i) => { const it = items[Math.max(0, Math.min(items.length - 1, i))]; track.scrollTo({ left: it.offsetLeft - track.offsetLeft }); };\n    const update = () => {\n      const i = current();\n      if (prev) prev.disabled = track.scrollLeft < 4;\n      if (next) next.disabled = track.scrollLeft + track.clientWidth >= track.scrollWidth - 4;\n      dots?.querySelectorAll('button').forEach((b, k) => b.setAttribute('aria-current', String(k === i)));\n    };\n    prev?.addEventListener('click', () => go(current() - 1));\n    next?.addEventListener('click', () => go(current() + 1));\n    dots?.addEventListener('click', (e) => { const b = e.target.closest('button'); if (b) go([...dots.children].indexOf(b)); });\n    track.addEventListener('keydown', (e) => {\n      if (e.key === 'ArrowRight') { e.preventDefault(); go(current() + 1); }\n      else if (e.key === 'ArrowLeft') { e.preventDefault(); go(current() - 1); }\n    });\n    let t = 0;\n    track.addEventListener('scroll', () => { cancelAnimationFrame(t); t = requestAnimationFrame(update); }, { passive: true });\n    update();\n  });\n});",
  "install": {
    "css": "https://mian.ro/dist/mian.css",
    "js": "https://mian.ro/dist/mian.js",
    "registryItem": "https://mian.ro/r/carousel.json"
  }
}
