Skip to main content
Copy for LLM

Replay Button

Re-runs (or resets) a demo by replacing the wrapping element's inner HTML with a saved snapshot, then calling Alpine.initTree() so every Alpine scope re-binds. Pairs with the data-replayable="true" attribute emitted by any WireKit preview that can be re-run or reset — animation-capable components (<x-wirekit::reveal>, <x-wirekit::stat animate>, animated card / hero / cta / feature / footer, chart) and state-mutating components whose demo is "used up" by interaction (a dismissible <x-wirekit::badge> or <x-wirekit::alert>).

Why this component exists: it primarily powers WireKit's own documentation site — docs.wirekit.app wraps every re-runnable example in the replay pattern and renders a ↻ Replay control on top. Since it ships in the package regardless, it doubles as a ready-made building block for your own app: wire it up to re-run an animation, reset an interactive demo to its starting state, or restore a dismissed badge or alert. Combine it with the data-replayable contract below and use it however fits your UI.

How it works

The pattern requires three pieces:

  1. A wrapper element carrying data-replay-target and data-replay-source="<encoded snapshot>".
  2. Inside that wrapper: the actual demo (<x-wirekit::reveal>, <x-wirekit::stat animate>, etc.) which emits data-replayable="true".
  3. The <x-wirekit::replay-button> itself, inside that same wrapper. closest() matches the element itself or one of its ancestors and never a sibling, so a button placed next to the target instead of within it finds nothing — and finding nothing is silent: the button still renders, still focuses, still activates, and does nothing at all.

On click, the button walks closest('[data-replay-target]') from itself, replaces that element's innerHTML with the snapshot, then calls Alpine.initTree(root) to re-mount.

The docs site auto-injects this pattern around every :::preview block whose source matches a data-replayable shape, so developers reading the docs see a "↻ Replay" button on every re-runnable or resettable example — an animated demo, or a dismissible badge / alert whose chips can be restored after they are dismissed.

Usage

The simplest shape — drop an animated demo into a :::preview block and the docs site auto-wires the replay-target wrapper around it:

Replay-aware reveal animation
Hello!

For developers wiring the pattern manually in their own app (outside the docs auto-injection), the full shape is:

@php
    // Render the demo AND its button once, so the snapshot and the first paint
    // cannot drift apart.
    $demo = Blade::render(
        '<x-wirekit::reveal preset="fade">Hello!</x-wirekit::reveal>'
        .'<x-wirekit::replay-button />'
    );
@endphp

<div data-replay-target data-replay-source="{{ $demo }}">
    {!! $demo !!}
</div>

The replay assigns the snapshot to the target's innerHTML, which replaces everything inside it — the button included. So the button has to be part of the snapshot as well as part of the initial render; leave it out and the first click deletes the control that triggered it.

The data-replay-source attribute MUST hold the rendered markup, HTML-escaped: the handler reads it back through dataset and assigns it to innerHTML, and innerHTML does not run Blade, so a raw <x-wirekit::…> tag in the snapshot comes back as an unknown element rather than as the component. Blade's {{ }} echo does the escaping on its own — do not wrap it in e(...) as well, which escapes twice and restores the snapshot as visible angle-bracket text.

The button picks up the closest data-replay-target ancestor automatically. To target a specific selector instead, pass target="…" — that selector is also resolved with closest(), so it too must match the button's own element or one of its ancestors:

<x-wirekit::replay-button target="[data-demo-root]" />

Customizing the icon

Pass slot content to override the default circular-arrow glyph:

<x-wirekit::replay-button>
    <x-wirekit::icon name="refresh" size="sm" />
    Replay
</x-wirekit::replay-button>

Style hooks

Single BEM root: wk-replay-button. Override via your app.css:

.wk-replay-button {
    color: var(--color-wk-text-subtle);
    transition: color var(--transition-wk-duration);
}

.wk-replay-button:hover {
    color: var(--color-wk-text);
}

Props

Prop Type Default Notes
label string 'Replay' Becomes aria-label on the rendered <button>.
target string|null null Optional CSS selector; when set, the click handler walks $el.closest(selector) instead of [data-replay-target].

Accessibility

  • Default aria-label="Replay" (overridable via the label prop).
  • Renders as a real <button type="button"> so keyboard activation (Space / Enter) works without extra wiring.
  • Dispatches wirekit:replayed (bubbling) on the data-replay-target root after re-mount; listeners can hook into the event for analytics / focus management.

Keyboard Interaction

Key Action
Tab Move focus to / away from the button (native browser tab order).
Space Activate the replay (re-mount the demo).
Enter Activate the replay (re-mount the demo).

Focus survives a replay, and it takes a deliberate step to do so. The button sits inside the target whose innerHTML is replaced, so the element you just activated is destroyed and rebuilt as a different node — left alone, the browser would drop focus to the document body and your next Tab would restart at the top of the page. The component therefore re-focuses the rebuilt button itself whenever the button held focus at activation, which is exactly the keyboard case; a pointer activation that never focused the button moves nothing. If the snapshot happens to carry no replay button, focus lands on the target instead, so you stay at the demo either way.

That restore runs before wirekit:replayed is dispatched, so it is a default rather than a fight: listen for the event and move focus onto the demo's first interactive element if that reads better for your page.

Updated in WireKit v2.61.0 (2026-09-30)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store