{
  "$schema": "https://mian.ro/schema/v2/registry.json",
  "schemaVersion": "2.0.0",
  "generatedAt": "2026-10-02T13:14:06.957Z",
  "id": "toast",
  "name": "Toast",
  "category": "Feedback",
  "description": "Temporary notifications. Mian.toast() from script, or data-mn-toast on a button. Pause on hover, stack, dismiss.",
  "tags": [
    "toast",
    "snackbar",
    "notification",
    "flash message",
    "sonner"
  ],
  "requiresJs": true,
  "license": "LicenseRef-MIAN-1.0",
  "composition": {
    "role": null,
    "intents": [
      "toast notification",
      "snackbar",
      "saved message",
      "temporary notification"
    ],
    "prominence": null,
    "density": null,
    "worksAfter": [],
    "worksBefore": [],
    "alternatives": [],
    "avoid": null
  },
  "url": "https://mian.ro/components/toast",
  "markdownUrl": "https://mian.ro/ai/components/toast.md",
  "classes": [
    {
      "name": "Mian.toast({ title, description, color, duration, action, position })",
      "description": "Creates a toast. Returns { dismiss, element }."
    },
    {
      "name": "data-mn-toast=\"Title\"",
      "description": "Declarative trigger; add data-mn-toast-description and data-mn-toast-color."
    },
    {
      "name": "mn-toaster[data-position]",
      "description": "bottom-right (default), top-right, top-center, bottom-center."
    }
  ],
  "accessibility": [
    "The toaster is a polite live region; danger toasts use role=\"alert\". Timers pause on hover and focus so there is time to read and act.",
    "Never put the only path to an action in a toast."
  ],
  "examples": [
    {
      "title": "Triggers",
      "description": null,
      "html": "<button class=\"mn-btn mn-btn-outline\" data-mn-toast=\"Event created\" data-mn-toast-description=\"Sunday, 14 December at 09:00\">Default</button>\n<button class=\"mn-btn mn-btn-outline\" data-mn-toast=\"Saved\" data-mn-toast-description=\"Your changes are live.\" data-mn-toast-color=\"success\">Success</button>\n<button class=\"mn-btn mn-btn-outline\" data-mn-toast=\"Could not connect\" data-mn-toast-description=\"Check your network and try again.\" data-mn-toast-color=\"danger\">Error</button>"
    },
    {
      "title": "From script, with an action",
      "description": null,
      "html": "<button class=\"mn-btn\" onclick=\"Mian.toast({ title: 'Message archived', action: { label: 'Undo', onClick: () => Mian.toast({ title: 'Restored', color: 'success' }) } })\">Archive</button>"
    }
  ],
  "css": ".mn-toaster {\n  position: fixed; z-index: 100; inset-block-end: 1rem; inset-inline-end: 1rem;\n  display: flex; flex-direction: column-reverse; gap: .6rem;\n  width: min(24rem, calc(100vw - 2rem));\n  pointer-events: none;\n}\n.mn-toaster[data-position=\"top-right\"] { inset-block: 1rem auto; flex-direction: column; }\n.mn-toaster[data-position=\"top-center\"] { inset-block: 1rem auto; inset-inline: 50% auto; translate: -50% 0; flex-direction: column; }\n.mn-toaster[data-position=\"bottom-center\"] { inset-inline: 50% auto; translate: -50% 0; }\n.mn-toast {\n  --_t: var(--_ct, var(--mn-fg));\n  pointer-events: auto;\n  display: grid; grid-template-columns: auto 1fr auto; gap: .15rem .7rem; align-items: start;\n  padding: .85rem .9rem;\n  border: var(--mn-border-w) solid var(--mn-border); border-radius: var(--mn-radius-box);\n  background: var(--mn-popover); color: var(--mn-fg);\n  box-shadow: var(--mn-shadow-lg);\n  font-size: var(--mn-text-sm); line-height: 1.45;\n  animation: var(--mn-enter) calc(var(--mn-enter-duration) + 40ms) var(--mn-ease-enter);\n}\n.mn-toast:not(:has(.mn-toast-icon)) { grid-template-columns: 1fr auto; }\n.mn-toast[data-leaving] { animation: mn-toast-out 180ms var(--mn-ease) forwards; }\n.mn-toast-icon { grid-row: span 2; color: var(--_t); padding-top: .1rem; display: flex; }\n.mn-toast-icon svg { width: 1.1rem; height: 1.1rem; }\n.mn-toast-title { font-weight: 600; }\n.mn-toast-desc { color: var(--mn-muted); grid-column: -3 / -2; }\n.mn-toast:not(:has(.mn-toast-icon)) .mn-toast-desc { grid-column: 1; }\n.mn-toast-close { grid-row: 1 / span 2; grid-column: -2; align-self: start; display: grid; place-items: center; width: 1.6rem; height: 1.6rem; margin: -.2rem -.3rem 0 0; border: 0; border-radius: 6px; background: none; color: var(--mn-muted); cursor: pointer; }\n.mn-toast-close:hover { background: var(--mn-hover); color: var(--mn-fg); }\n.mn-toast-close:focus-visible { outline: none; box-shadow: var(--mn-focus); }\n.mn-toast-close svg { width: .9rem; height: .9rem; }\n.mn-toast-action { margin-top: .5rem; grid-column: -3 / -2; justify-self: start; }\n@keyframes mn-toast-in { from { opacity: 0; transform: translateY(8px) scale(.97); } }\n@keyframes mn-toast-out { to { opacity: 0; transform: translateX(16px); } }",
  "js": "/* Mian.toast({ title, description, color, duration, action: { label, onClick } }) */\nconst ICONS = {\n  success: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"m8 12 3 3 5-6\"/></svg>',\n  danger: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 8v5M12 16.5v.01\"/></svg>',\n  warning: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3 2 20h20L12 3Z\"/><path d=\"M12 10v4M12 17v.01\"/></svg>',\n  info: '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 11v5M12 8v.01\"/></svg>',\n};\nconst X = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6 6 18\"/></svg>';\n\nfunction toaster(position) {\n  let t = document.querySelector('.mn-toaster');\n  if (!t) {\n    t = document.createElement('section');\n    t.className = 'mn-toaster';\n    t.setAttribute('aria-label', 'Notifications');\n    t.setAttribute('aria-live', 'polite');\n    document.body.appendChild(t);\n  }\n  if (position) t.dataset.position = position;\n  return t;\n}\n\nconst esc = (s) => String(s).replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]));\n\nfunction toast(opts = {}) {\n  if (typeof opts === 'string') opts = { title: opts };\n  const { title = '', description = '', color = '', duration = 5000, action = null, position } = opts;\n  const t = toaster(position);\n  const el = document.createElement('div');\n  el.className = 'mn-toast' + (color ? ` mn-${color}` : '');\n  el.setAttribute('role', color === 'danger' ? 'alert' : 'status');\n  el.innerHTML =\n    (ICONS[color] ? `<span class=\"mn-toast-icon\" aria-hidden=\"true\">${ICONS[color]}</span>` : '') +\n    (title ? `<div class=\"mn-toast-title\">${esc(title)}</div>` : '') +\n    (description ? `<div class=\"mn-toast-desc\">${esc(description)}</div>` : '') +\n    `<button type=\"button\" class=\"mn-toast-close\" aria-label=\"Dismiss notification\">${X}</button>`;\n  if (action) {\n    const b = document.createElement('button');\n    b.type = 'button'; b.className = 'mn-btn mn-btn-sm mn-btn-outline mn-toast-action';\n    b.textContent = action.label;\n    b.addEventListener('click', () => { action.onClick?.(); dismiss(); });\n    el.appendChild(b);\n  }\n  let timer = 0, left = duration, started = Date.now();\n  const dismiss = () => {\n    if (el.dataset.leaving) return;\n    el.dataset.leaving = '1';\n    clearTimeout(timer);\n    const done = () => el.remove();\n    if (matchMedia('(prefers-reduced-motion: reduce)').matches) done();\n    else { el.addEventListener('animationend', done, { once: true }); setTimeout(done, 400); }\n  };\n  const arm = () => { if (duration > 0) { started = Date.now(); timer = setTimeout(dismiss, left); } };\n  const pause = () => { clearTimeout(timer); left -= Date.now() - started; };\n  el.querySelector('.mn-toast-close').addEventListener('click', dismiss);\n  el.addEventListener('pointerenter', pause); el.addEventListener('pointerleave', arm);\n  el.addEventListener('focusin', pause); el.addEventListener('focusout', arm);\n  t.appendChild(el);\n  // Keep the stack short.\n  const all = t.querySelectorAll('.mn-toast:not([data-leaving])');\n  if (all.length > 4) all[0].querySelector('.mn-toast-close').click();\n  arm();\n  return { dismiss, element: el };\n}\n\nMian.toast = toast;\ndocument.addEventListener('click', (e) => {\n  const b = e.target.closest('[data-mn-toast]');\n  if (!b) return;\n  const d = b.dataset;\n  toast({ title: d.mnToast, description: d.mnToastDescription || '', color: d.mnToastColor || '', position: d.mnToastPosition });\n});",
  "install": {
    "css": "https://mian.ro/dist/mian.css",
    "js": "https://mian.ro/dist/mian.js",
    "registryItem": "https://mian.ro/r/toast.json"
  }
}
