{
  "$schema": "https://mian.ro/schema/v2/registry.json",
  "schemaVersion": "2.0.0",
  "generatedAt": "2026-10-02T13:14:06.957Z",
  "id": "hold-button",
  "name": "Hold to confirm",
  "category": "Actions",
  "description": "A button that must be held for a moment before it acts. For actions that are hard to undo, without a second dialog.",
  "tags": [
    "hold to confirm",
    "press and hold",
    "delete button",
    "destructive action",
    "confirm",
    "long press",
    "safety"
  ],
  "requiresJs": true,
  "license": "LicenseRef-MIAN-1.0",
  "composition": {
    "role": null,
    "intents": [
      "hold to delete",
      "press and hold to confirm",
      "safe destructive action"
    ],
    "prominence": null,
    "density": null,
    "worksAfter": [],
    "worksBefore": [],
    "alternatives": [
      "modal"
    ],
    "avoid": "Not for everyday actions: holding is slow on purpose."
  },
  "url": "https://mian.ro/components/hold-button",
  "markdownUrl": "https://mian.ro/ai/components/hold-button.md",
  "classes": [
    {
      "name": "mn-hold",
      "description": "On an mn-btn, with data-mn-hold. A fill sweeps across while it is held."
    },
    {
      "name": "--_hold",
      "description": "How long to hold. Default 1200ms."
    },
    {
      "name": "data-mn-hold-done",
      "description": "Text to show once it has fired."
    },
    {
      "name": "mn:confirm",
      "description": "The event to listen for. A plain click does nothing."
    }
  ],
  "accessibility": [
    "Works with a pointer, and with Space or Enter held down.",
    "Say “hold” in the label, so the behaviour is not a surprise.",
    "Releasing early cancels; nothing happens on a quick click or tap.",
    "Still offer undo where you can: holding prevents accidents, it does not replace recovery."
  ],
  "examples": [
    {
      "title": "Delete",
      "description": null,
      "html": "<div class=\"mn-row\"><button class=\"mn-btn mn-danger mn-hold\" type=\"button\" data-mn-hold data-mn-hold-done=\"Deleted\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14\"/></svg> Hold to delete</button><button class=\"mn-btn mn-btn-outline mn-hold\" type=\"button\" data-mn-hold data-mn-hold-done=\"Published\" style=\"--_hold:800ms\">Hold to publish</button></div>"
    }
  ],
  "css": ".mn-hold { --_hold: 1200ms; position: relative; overflow: hidden; isolation: isolate; }\n.mn-hold::before { content: \"\"; position: absolute; inset: 0; z-index: -1; background: color-mix(in oklab, var(--_fg, currentColor) 26%, transparent); transform: scaleX(0); transform-origin: left center; transition: transform 180ms var(--mn-ease-out); }\n.mn-hold[data-holding]::before { transform: scaleX(1); transition: transform var(--_hold) linear; }\n.mn-hold[data-done]::before { transform: scaleX(1); transition: none; }\n@media (prefers-reduced-motion: reduce) { .mn-hold[data-holding]::before { transition-duration: var(--_hold); } }",
  "js": "/* <button class=\"mn-btn mn-danger mn-hold\" data-mn-hold style=\"--_hold:1200ms\">Hold to delete</button>\n   Fires mn:confirm only after the button was held for the whole time (pointer,\n   or Space / Enter held). Releasing early cancels. data-done marks it done. */\nregister('hold-button', (root) => each(root, '[data-mn-hold]', 'Hold', (b) => {\n  let timer = 0;\n  const ms = () => parseFloat(getComputedStyle(b).getPropertyValue('--_hold')) || 1200;\n  const start = () => { if (timer || b.disabled || b.hasAttribute('data-done')) return; b.setAttribute('data-holding', ''); timer = setTimeout(() => { timer = 0; b.removeAttribute('data-holding'); b.setAttribute('data-done', ''); if (b.dataset.mnHoldDone) b.textContent = b.dataset.mnHoldDone; emit(b, 'confirm'); }, ms()); };\n  const cancel = () => { if (!timer) return; clearTimeout(timer); timer = 0; b.removeAttribute('data-holding'); };\n  b.addEventListener('pointerdown', start);\n  for (const ev of ['pointerup', 'pointerleave', 'pointercancel', 'blur']) b.addEventListener(ev, cancel);\n  b.addEventListener('keydown', (e) => { if ((e.key === ' ' || e.key === 'Enter') && !e.repeat) { e.preventDefault(); start(); } });\n  b.addEventListener('keyup', (e) => { if (e.key === ' ' || e.key === 'Enter') cancel(); });\n  b.addEventListener('click', (e) => { if (!b.hasAttribute('data-done')) e.preventDefault(); });\n}));",
  "install": {
    "css": "https://mian.ro/dist/mian.css",
    "js": "https://mian.ro/dist/mian.js",
    "registryItem": "https://mian.ro/r/hold-button.json"
  }
}
