Slot Summary
What the system understood, before it acts
- MemberA. Okonkwo
- PlanPPO GoldFrom records
- Service date14 MarchInferredCheck this
- RegionNortheastDefault
- AuthorizationStill needed
What it is
The read-back an agent shows just before it acts: one row per parameter it resolved, each row carrying how it got there. Values the user actually said are left unmarked; anything the model inferred, defaulted or looked up wears a badge saying so. Rows the system could not fill stay on screen as a visible ask rather than quietly disappearing, and each row carries its own correction control.
Why it matters
It is the difference between a user auditing an agent and a user rubber-stamping it. If an inferred value renders identically to one the user supplied, they scan the list, recognise their own words, and approve four things the model guessed at — which is the failure mode this surface exists to prevent. Google Assistant scales exactly this read-back to how reversible the action is, Rasa gives correction and slot validation first-class flows rather than treating a fix as a restart, and ServiceNow Horizon puts the same resolved-parameter card in front of its AI actions.
Observed in: Google Assistant · Rasa · ServiceNow Horizon
Anatomy
- 1
slot-summaryRoot container holding the row list and, when handlers are supplied, the action footer. - 2
slot-summary-slotOne resolved parameter. Carriesdata-source(stated / inferred / defaulted / retrieved) and, when it has no value,data-missing— both are addressable in tests without reaching for text. - 3
slot-summary-blockedThe outstanding-count line beside the footer. Renders only while a required slot is still empty, and disappears the moment the frame is complete.
How to use it
Reach for it in the beat between an agent understanding a request and acting on it, whenever the action has a side effect worth being sure about — sending, scheduling, cancelling, spending. Pass one `Slot` per parameter with a `source` saying how it was filled; mark the ones the action cannot proceed without as `required`, and leave their `value` undefined rather than dropping them from the array. Supply `onCorrect` to give every row an in-place fix, and `onConfirm` with a `confirmLabel` that names the consequence. For a reversible action, skip this surface entirely — a read-back before something the user can simply undo is friction charging for nothing.
Do
Handle onCorrect by editing the one slot it names and leaving the rest of the frame alone — a correction is a repair, not a restart.
- Recipientdesign-team (14 people)From records
- MessageStandup moves to 10:00 from Monday
- Send atTomorrow, 9:00 AMInferredCheck this
- ChannelEmailDefault
Keep a required slot you could not fill in the array with no value, so it renders as an ask and holds the confirm action closed until it is answered.
- RecipientStill needed
- MessageStandup moves to 10:00 from Monday
- ChannelEmailDefault
1 still needed
Don't
Do not filter unresolved slots out of the array — a summary that shows only what it managed to fill reads as complete, and lets the agent act on a partial frame.
- MessageStandup moves to 10:00 from Monday
- Send atTomorrow, 9:00 AMInferred
- ChannelEmailDefault
Do not leave confirmLabel at its default when the action has a real consequence — name what happens, so the last click is a decision rather than a reflex.
- Recipientdesign-team (14 people)From records
- MessageStandup moves to 10:00 from Monday
- Send atTomorrow, 9:00 AMInferredCheck this
- ChannelEmailDefault
Accessibility
- Keyboard
- Passing `onCorrect` puts a correction button on *every* row, not on the rows that need one — an eight-slot summary is eight tab stops before you reach the footer. There is no per-slot opt-out, so the only way to shorten the run is to hand in fewer slots.
- The correction, confirm and cancel controls are real buttons, so Space and Enter activate them. Nothing else is bound: no arrow-key movement between rows, no Escape on Cancel, no Enter-to-confirm from a row.
- While any required slot is empty the confirm button carries the native `disabled` attribute, so it is skipped by Tab entirely. A keyboard user goes from the last row straight to Cancel and is given no reason why.
- Rows are `<li>` elements and are not focusable. A value, its source badge and its low-confidence flag are read-only text, reachable only by reading the row — there is no keyboard path that lands on a slot.
- Screen reader
- The rows are a plain `<ul>`, so the summary announces as a list with a count. Within a row the label, the value and the badges are three separate runs of text with no programmatic association — a row reads as "Send at, 9:00am, Inferred" because of source order, not because anything marks the label as the name of that value.
- The correction button is named by `aria-label={`Change ${slot.label}`}` — or `Add ${slot.label}` when the slot is empty. `label` is typed `React.ReactNode`, so an element there ships a button announced as "Change [object Object]"; only a plain string survives.
- The source badges are ordinary text, so "Inferred", "Default" and "From records" are announced in the row. A `stated` slot has no badge, which means an unmarked row and a user-supplied row are indistinguishable to assistive tech — that absence is the design, and it is why every other source has to keep its badge.
- The low-confidence flag announces as "Check this"; its warning triangle is `aria-hidden`. It is suppressed on a slot with no value, so a row that is both empty and doubtful announces only "Still needed".
- The blocked count — "2 still needed" — is plain text with no live region, and it is not wired to the confirm button by `aria-describedby`. Filling the last required slot silently enables the action and emptying one silently disables it; neither transition is announced.
- Focus
- Nothing here moves focus. A correction re-renders the row in place, so the button that was clicked is still there and still focused — which is the point, and which also means the responsibility for taking the user somewhere useful sits in your `onCorrect`.
- Filling the last required slot changes the tab order rather than focus: the confirm button appears in it, between wherever the user is and wherever they were heading. Expect the run to get one stop longer mid-interaction.
- Every control is the vendored `Button`, which ships `focus-visible:ring-3`. This surface has a visible focus indicator without anything at the call site.
Watch out for
- Slot
labelis typed asReact.ReactNode, but the per-row correction button builds its accessible name by interpolating it into a string —Change ${slot.label}. Pass a plain string. An element such aslabel={<span>Send at</span>}type-checks and renders correctly, and then ships a button announced as "Change [object Object]". - The confirm button's disabled state is derived, never passed: it is computed from the slots that are
requiredwith novalue. There is no prop that unblocks it, which is deliberate — the only way to enable the action is to fill the slot, and the only way to break that rule is to stop marking the slot required. - The low-confidence flag is suppressed on a slot with no value — the badge is guarded on the row having something to doubt. A slot that is both empty and low-confidence renders only as "Still needed", so do not rely on
confidenceto draw attention to an absence. - A
sourceof "stated" renders no badge at all. That is the intended reading, not a missing case: marking every row is the same as marking none, so the quiet rows are the ones the user supplied and the badged rows are the ones that need checking.
Installation
npx shadcn@latest add https://super-ai-components.vercel.app/r/slot-summary.jsonRequires a shadcn app on Base UI (style base-nova). Radix-based styles such as new-york fail to typecheck: these components use Base UI's render prop and callbacks like onOpenChangeComplete, which Radix primitives do not have.