Skip to content

Suggestions

<aparte-suggestions>

Class: AparteSuggestions

Prompt starters — a row of suggested prompts a reader clicks instead of typing.

Every chat product opens on three or four of these, and the example app used to hand-roll them: four <button class="chip">, a click handler, a CSS recipe of its own. This element is that pattern done once, wearing the button recipe.

A click goes THROUGH the composer — setValue() then submit() — never a synthetic aparte-send. The composer’s submit() is where every gate lives (disabled, already streaming, requireModelSelection), and a chip that bypassed them once sent a request with an empty model id while the composer was visibly greyed out. So the element needs a composer: the nearest <aparte-composer> ancestor, else the one serving the chat named by target, else the first one in the document.

Tier (a) of ratified decision #8: core honours the click alone, so it is live by default. aparte-suggestion fires first and is cancelable — preventDefault() leaves the composer untouched, for an app that wants the prompt for itself.

It declares no custom property of its own: the gap comes from the global --aparte-space-* tokens and the chips from the aparte-btn recipe, so it inherits a theme rather than exposing knobs to re-set.

Width Open in a tab
The real element, in its own document so this site's CSS cannot reach it. It renders the same example printed below — every framework mounts this same element, so the rendering is what you get in all five; the code is what changes.
<!-- The starters sit above the input, inside the composer; a click fills and sends. -->
<aparte-composer>
<aparte-suggestions empty-only
suggestions='["What is aparté?", {"label": "Write a haiku", "prompt": "Write a haiku about web components."}]'>
</aparte-suggestions>
<div class="aparte-composer-shell">
<div class="aparte-composer-row">
<aparte-composer-input></aparte-composer-input>
<aparte-composer-send></aparte-composer-send>
</div>
</div>
</aparte-composer>
AttributeDescription
suggestionsThe starters, as a JSON array: strings, or { "label", "prompt" } objects when the visible label and the sent text differ. The suggestions PROPERTY takes the same shape without the JSON.
targetThe id of the <aparte-chat> whose composer should receive the click, when the element is not inside that composer.
empty-onlyShows the row only while the conversation it serves is empty: hidden (hidden) once a message is in its transcript or on its way — a thread being loaded counts — and shown again when the transcript is cleared, by a new chat for instance. The composer’s send hides it at once, before its message lands. The element sets and lifts hidden itself and never lifts one you set. With no transcript to read — a composer outside any chat — the first send hides it until the attribute is removed.
modesend (default): the click fills the composer and submits. fill: the click fills the composer and focuses it, so the reader edits first.
data-emptyReflected BY the element while it has no suggestion to show; the stylesheet hides it then. Read-only, do not set it yourself.
PropertyTypeDescription
suggestionsAparteSuggestion[]Takes the array, or the JSON string the attribute documents. Both, because both arrive: React 19 and Svelte assign the PROPERTY whenever the element has one of that name, so <aparte-suggestions suggestions='[…]'> — the documented spelling, typed valid by both wrappers — reached this setter as a string, Array.isArray said no, and the row rendered nothing while the attribute (and so attributeChangedCallback) was never written.
EventTypeDescription
aparte-suggestionCustomEvent&lt;AparteSuggestionEventDetail>A starter was clicked. Bubbles, and is cancelable: preventDefault() stops the composer from being filled or submitted.

The element is the same object everywhere — the tag does not change. What changes is how an attribute is written and how an event reaches you.

<aparte-suggestions empty-only=""></aparte-suggestions>
el.addEventListener('aparte-suggestion', (e) => use(e.detail));

Installation and the framework-specific traps: React · Vue · Svelte · Angular.