Branch Switcher
When a model regenerates an answer, the earlier one is not waste — it is the other option. The branch switcher is how a reader moves between them: two arrows, a count, and an announcement that says the whole sentence rather than a fragment.
It is a control, not a container. You render whichever variant is active; the switcher owns the index and hands it back.
Basic Usage
The index is 1-based everywhere — in the label the reader sees, in the value wire:model
hands the server, and in the current prop you write. One numbering, no conversions.
Wiring It to Livewire
wire:model reaches the index itself. Bind it, render the active variant, and the reader's move
arrives as an ordinary property update.
The component holds the index and the answers:
<?php
namespace App\Livewire;
use Livewire\Component;
class AnswerVariants extends Component
{
// 1-based, like the switcher: 1 is the first answer.
public int $branch = 1;
// One entry per generated answer. A regeneration appends to it.
public array $answers = [
'Sixty requests a minute, per token.',
'Sixty a minute per token, and the 429 says how long to wait.',
];
}
The view binds the switcher and renders the answer it points at:
{{-- 1. The switcher owns which one is showing; you own what that means. --}}
<x-wirekit::branch-switcher
wire:model.live="branch"
:count="count($this->answers)"
:current="$branch"
/>
{{-- 2. Render the active variant. The index is 1-based, so subtract one for a zero-based list. --}}
<x-wirekit::assistant-message model="atlas-2">
{{ $this->answers[$branch - 1] }}
</x-wirekit::assistant-message>
Without Livewire, listen for the event instead:
// 1. Bubbles, so one listener on the conversation covers every turn in it.
document.addEventListener('wirekit:branch-switcher:change', (event) => {
// 2. `detail` carries both numbers, so a handler never has to re-read the DOM.
showVariant(event.detail.current, event.detail.total);
});
Under an Answer
The switcher belongs where the reader looks after reading — under the turn, beside the other per-answer controls.
Retry-After header.
The Ends Do Not Wrap
A reader stepping through variants expects the last one to be the last one. Silent wrap-around loses that, so the arrows disable at the ends instead.
Pass loop where cycling is what you want — a carousel of drafts rather than a sequence.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
count |
int |
0 |
How many variants there are. 0 renders nothing at all. |
current |
int |
1 |
Which one is showing, 1-based. Out of range is clamped, never refused. |
label |
string |
null |
Names the group. Falls back to a translated default. |
loop |
bool |
false |
Whether the ends wrap. |
scope |
string |
null |
Personalization scope, as on every component. |
Events
| Event | Detail | When |
|---|---|---|
wirekit:branch-switcher:change |
{ current, total } |
A move settled. Bubbles. |
Accessibility
- The group carries a name. An unset
labelfalls back to a translated default rather than to silence, because a group of buttons with no name is announced as a group of nothing. - Each move announces the whole sentence — "Showing response 2 of 3" — into a polite region that exists before it has anything to say. A live region created together with its text is inert, and a fragment appended to an existing one tells a screen reader's user nothing.
- The count beside the arrows is a plain label, not a live region. Making it live would interrupt the reader mid-move, and the announcer already speaks once the move has settled.
- ← and → work while either arrow has focus, so the keyboard reaches the whole control without leaving it.
- Both arrows meet the touch-target floor the rest of the library uses.
Keyboard Interaction
| Key | Action |
|---|---|
| ← | Previous variant. |
| → | Next variant. |
| Enter / Space | Activates the focused arrow. |
See Also
- Assistant Message — the turn the variants belong to.
- Tool Call — what the assistant did before it answered.
- Carousel — for stepping through content rather than through answers.