Hold Button <mv-hold-button>
Botão “Segure para confirmar” para ações destrutivas: enquanto ele é pressionado, um líquido sobe por dentro (ou entra pela lateral) e inverte o rótulo onde o cobre, soltar antes da hora o esvazia, e segurar até o fim termina com uma marca de seleção, um rótulo de concluído e mv-confirm.
| Categoria | Microinterações |
|---|---|
| Tipo | Web Component (<mv-hold-button>) |
| Status | estável |
| Keywords | hold, long-press, confirm, destructive, delete, progress, safety, liquid |
When to use
- A destructive action such as deleting an account or project should require a deliberate press and hold
- An important but non-destructive action should be protected from accidental clicks with an accent hold gauge
Avoid when
- The user must read consequences or type a confirmation before proceeding → use Dialog instead
- The action stops a long-running process and users should see what they keep before stopping → use Salvage Stop instead
- The action is frequent and harmless; forcing a hold slows users down for no benefit → use Button instead
Instalação
node scripts/add.mjs hold-button --out ./src/marvelousAgente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["hold-button"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, components/hold-button/hold-button.js, components/hold-button/hold-button.css.
Uso
Início rápido, a menor marcação que funciona:
<mv-hold-button done-label="Project deleted">Hold to delete</mv-hold-button>Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:
<div style="display:grid;gap:1.25rem;width:100%;max-width:440px">
<div style="display:grid;gap:.9rem;padding:1.1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
<div style="display:grid;gap:.25rem">
<strong style="font-size:.95rem">Delete “Lumen website redesign”</strong>
<span style="font-size:.82rem;color:var(--mv-fg-muted)">All 48 files and their history will be permanently deleted.</span>
</div>
<div style="display:flex;gap:.6rem;align-items:center;flex-wrap:wrap">
<mv-hold-button duration="1600" done-label="Project deleted" id="hb-demo-delete">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M10 11v6M14 11v6M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2l1-12M9 7V4h6v3"/></svg>
Hold to delete
</mv-hold-button>
<span id="hb-demo-log" style="font-size:.8rem;color:var(--mv-fg-subtle)">Let go early to cancel.</span>
</div>
</div>
<div style="display:flex;gap:.75rem;align-items:center;justify-content:center;flex-wrap:wrap">
<mv-hold-button data-variant="accent" data-fill="sweep" duration="1000" done-label="Published" style="--mv-hold-button-radius:var(--mv-radius-full)">Hold to publish</mv-hold-button>
</div>
</div>
<script type="module">
const el = document.getElementById("hb-demo-delete");
el?.addEventListener("mv-confirm", () => {
const log = document.getElementById("hb-demo-log");
if (log) log.textContent = "mv-confirm received at " + new Date().toLocaleTimeString("en-US");
});
</script>API
Attributes
| Name | Tipo | Default | Description |
|---|---|---|---|
duration | number | 1500 | Required hold time (ms). |
done-label | string | Confirmed | Label (and announcement) once confirmed. |
hint | string | Press and hold to confirm | Instruction: accessible description, and the bubble shown after a tap that did not hold. |
reset | number | 2400 | Return to the initial state after confirmation (ms, 0 = stays confirmed). |
disabled | boolean | Disables the button. | |
data-variant | accent | (danger) | Accent color instead of danger (non-destructive actions). |
data-fill | sweep | (rise) | The liquid fills from the start edge instead of rising from the bottom. |
data-state | idle | holding | rewinding | armed | done | Set by the component. |
Properties
| Name | Tipo | Description |
|---|---|---|
progress | number | Progress 0..1 (read-only). |
strings | { hint?, done?, again? } | Overrides for the default texts (translation). The hint and done-label attributes win; again = "Press again to confirm". |
Methods
| Name | Description |
|---|---|
resetState({ instant }) | Returns to the initial state (rewinds, or instantly). |
Events
| Name | Description |
|---|---|
mv-confirm | The hold went all the way. |
Content structure
| Name | Description |
|---|---|
(content) | Label, SVG icon included. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-hold-button-color | var(--mv-danger) | Fill and text color. |
--mv-hold-button-fg | white | Text on the fill. |
--mv-hold-button-radius | var(--mv-radius-md) | Radius. |
Accessibility
A real <button> described by the instruction (aria-describedby). Keyboard: hold Space or Enter (key repeat is ignored, releasing drains the liquid, Escape or losing focus cancels). A tap never confirms: it shows the instruction and nudges the button. A click with no press behind it (screen readers, switch access) arms the button (“Press again to confirm”, announced, 5 s) and a second one confirms, so the action never depends on a timed gesture alone. Confirmation is announced by a role=status region and the button becomes aria-disabled while confirmed. Touch: no context menu during the press, scrolling cancels, a short vibration on completion where supported. Reduced motion: the level stays (it carries information), the rolling surface, the nudge and the slides go away. Forced colors: the liquid is drawn in Highlight with HighlightText on it.