<!-- The composer renders nothing of its own: this markup IS the component. The shell
draws the border, the row keeps the button on the text's bottom edge as it grows. -->
<aparte-composer placeholder="Ask anything…">
<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>The composer is a set of parts you arrange yourself. <aparte-composer> holds the state — the
text, the pending files, whether a reply is streaming — and every part reads it from there, at
any depth. It draws nothing of its own: the two classes in the example, .aparte-composer-shell
and .aparte-composer-row, give it the usual look, and without them the parts still work
wherever your layout puts them.
Inside an <aparte-chat>, the message goes to the chat’s client. On its
own, listen for aparte-send on the composer.
Anatomy
Section titled “Anatomy”<aparte-composer placeholder="Ask about this file…">
<div class="aparte-composer-shell">
<aparte-composer-attachments></aparte-composer-attachments>
<div class="aparte-composer-row">
<aparte-composer-add-attachment accept="image/*,.pdf,.md"></aparte-composer-add-attachment>
<aparte-composer-input></aparte-composer-input>
<aparte-composer-send></aparte-composer-send>
</div>
<aparte-composer-toolbar>
<aparte-composer-action icon="tool" label="Use tools" action-id="tools"></aparte-composer-action>
</aparte-composer-toolbar>
</div>
</aparte-composer>
<script>
// One file already picked, so the strip of pending files is on screen.
document.querySelector('aparte-composer')
.addAttachments([new File(['# Launch notes'], 'launch-notes.md', { type: 'text/markdown' })]);
</script>Variants
Section titled “Variants”With files attached
Section titled “With files attached”<aparte-composer placeholder="Ask about these files…">
<div class="aparte-composer-shell">
<!-- The pending files, above the field. Hidden while there are none. -->
<aparte-composer-attachments></aparte-composer-attachments>
<div class="aparte-composer-row">
<!-- Opens the file picker, and makes the whole composer a drop target. -->
<aparte-composer-add-attachment accept="image/*,.pdf,.md"></aparte-composer-add-attachment>
<aparte-composer-input></aparte-composer-input>
<aparte-composer-send></aparte-composer-send>
</div>
</div>
</aparte-composer>
<script>
// Two files already picked, standing in for what a reader would choose.
const composer = document.querySelector('aparte-composer');
const notes = new File(['# Launch notes'], 'launch-notes.md', { type: 'text/markdown' });
const canvas = Object.assign(document.createElement('canvas'), { width: 120, height: 80 });
const paint = canvas.getContext('2d');
paint.fillStyle = '#b07d33';
paint.fillRect(0, 0, 120, 80);
canvas.toBlob((blob) => composer.addAttachments([notes, new File([blob], 'cover.png', { type: 'image/png' })]));
</script><aparte-composer-add-attachment> opens the file picker and makes the whole composer a drop
target; <aparte-composer-attachments> shows the files waiting to be sent. They reach you on
aparte-send, as event.detail.files: reading or uploading them is your app’s job.
With a toolbar
Section titled “With a toolbar”<aparte-composer placeholder="Ask anything…">
<div class="aparte-composer-shell">
<div class="aparte-composer-row">
<aparte-composer-input></aparte-composer-input>
<aparte-composer-send></aparte-composer-send>
</div>
<!-- The bottom row, laid out in DOM order. An action is an icon button that only emits
aparte-action-click: what it does is yours. -->
<aparte-composer-toolbar>
<aparte-composer-action icon="tool" label="Use tools" action-id="tools"></aparte-composer-action>
<aparte-composer-action icon="image" label="Generate an image" action-id="image"></aparte-composer-action>
<!-- From @aparte/plugin-model-selector: lists the models your providers declare.
margin-inline-start: auto pushes it, and whatever follows, to the end of the row. -->
<aparte-model-selector auto-select persist style="margin-inline-start: auto"></aparte-model-selector>
</aparte-composer-toolbar>
</div>
</aparte-composer><aparte-composer-toolbar> is the row under the field for a model picker, a mode switch or your
own buttons. Children keep their DOM order; margin-inline-start: auto on one pushes it, and
everything after it, to the end of the row. An <aparte-composer-action> is an icon button that
only emits aparte-action-click: what it does is up to you.
While a reply streams
Section titled “While a reply streams”<aparte-composer placeholder="Ask anything…">
<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>
<script>
// AparteClient sets this while a reply streams; set by hand here to show the state.
// The send button becomes the stop button, and a click on it cancels the turn.
document.querySelector('aparte-composer').streaming = true;
</script>The send button becomes a stop button, and a click cancels the turn. The client sets
streaming for you; this demo sets it by hand. If stop should live somewhere else as well,
add <aparte-composer-cancel>.
<aparte-composer disabled=""></aparte-composer>el.addEventListener('aparte-send', (e) => use(e.detail));<aparte-composer disabled=""></aparte-composer>The aparte-* tags are typed JSX intrinsics as soon as you import from @aparte/react. A presence attribute takes '', never true — React stringifies it, and disabled={false} would render disabled="false", which hasAttribute reads as on. Events reach you by ref, typed through the DOM.
<template>
<aparte-composer disabled=""
@aparte-send="(e) => use(e.detail)"
></aparte-composer>
</template>Declared through Vue’s GlobalComponents, so vue-tsc checks the tag in any template. A presence attribute takes '' to set and null to remove, never false.
<aparte-composer disabled=""
on:aparte-send={(e) => use(e.detail)}
></aparte-composer>Declared through SvelteHTMLElements, so svelte-check covers the attributes and the on: handlers. A presence attribute takes '', never false.
import { AparteComposerDirective } from '@aparte/angular';<aparte-composer [disabled]="true"
(send)="use($event)"></aparte-composer>A standalone directive whose selector IS the tag, so the real element sits in the template — @if, @for and content projection all reach it — and no CUSTOM_ELEMENTS_SCHEMA is needed.
Installation and the framework-specific traps: React · Vue · Svelte · Angular.
Customize
Section titled “Customize”The field and the button read the input and button tokens, so a theme reaches them with no
selector: --aparte-input-bg, --aparte-input-border, --aparte-radius-input and
--aparte-input-focus-border for the field, --aparte-primary for the button, and
--aparte-composer-control-size for the height they share. Every variable is listed under
API below.
Enter sends and Shift+Enter starts a new line; submit-on-enter="false" swaps the two. A
button of your own can send or stop through composer.submit(), which cancels instead while a
reply streams:
composer.addEventListener('aparte-composer-change', (e) => {
const { streaming, disabled, value, attachments } = e.detail.state;
myButton.textContent = streaming ? 'Stop' : 'Send';
myButton.disabled = disabled || (!streaming && !value.trim() && attachments.length === 0);
});
myButton.addEventListener('click', () => composer.submit());<aparte-composer>
Section titled “<aparte-composer>”<aparte-composer> · class AparteComposer
Attributes
| Attribute | Description |
|---|---|
placeholder | Fallback placeholder for <aparte-composer-input>, which reads it off this element when it carries none of its own. Changing it here reaches the inputs already on the page too. |
disabled | Disables the composer’s own controls — the input, send, add-attachment and <aparte-composer-action> buttons each read it. What you put in the toolbar is yours to disable. |
target | The id of the <aparte-chat> this composer drives. |
submit-on-enter | Enter sends and Shift+Enter breaks the line (the default); set it to the string "false" to swap them. Read lazily by the submitOnEnter getter rather than observed, which is why it was missing from the manifest — and so from every typed surface — while all four wrappers wrote it. |
data-panel-active | Reflected BY the element while a panel (an elicitation, an approval) is mounted inside it; the shipped stylesheet keys off it. Read-only. |
data-panel-mode | Reflected BY the element while a panel is active: what the send button does meanwhile ('none' removes it — through this attribute, not by reaching into the button). Read-only. |
data-model-gated | Reflected BY the element when requireModelSelection is on and no model is selected: sending is blocked and the shipped stylesheet greys the composer. Read-only. |
Properties
| Property | Type | Description |
|---|---|---|
value (readonly) | string | |
streaming (readonly) | boolean | |
disabled | boolean | The attribute is the state, so the property writes it. It has to exist: React 19 and Svelte assign the PROPERTY whenever the element has one of that name, and a getter alone made <aparte-composer disabled=""> — a spelling the wrappers type as valid — throw and take the render down. presenceOn because '' is what those templates hand a presence property (#62). |
submitOnEnter (readonly) | boolean | When false, Shift+Enter submits and a bare Enter inserts a newline — the inverse of the default. Driven by the submit-on-enter attribute. |
attachments (readonly) | File[] | |
placeholder | string | Writes the attribute the inputs read (see disabled for why the setter exists). |
targetId (readonly) | string | null | |
panelActive (readonly) | boolean |
Methods
| Method | Description |
|---|---|
getState(): AparteComposerState | Snapshot of the composer’s observable state. Pair with the aparte-composer-change DOM event to drive a custom send button or footer control that lives outside the composer package: |
setValue(value: string): void | Set the composer’s value — both what a send will submit and what the editor shows. <aparte-composer-input> writes through any value it does not already hold, so this prefills the visible field (a template button, a restored draft) as readily as it stages text for an immediate submit(). |
addAttachments(files: FileList | File[]): void | Append files to the pending attachments and notify. Does not de-duplicate. |
removeAttachment(file: File): void | Drop one pending attachment and notify. Matched by IDENTITY — pass the same File object the composer handed you, not an equal one; two picks of the same file on disk are two distinct objects. |
clearAttachments(): void | Drop every pending attachment and notify. |
showPanel(panel: HTMLElement, options?: { submitEnabled?: boolean; onSubmit?: () => void; /** * What the send button means for this panel — ''none'' if it has no act * for it, in which case the button is not drawn. See * {@link AparteComposerPanelMode}. */ mode?: AparteComposerPanelMode; /** Called when something other than this owner closes the panel. */ onEvict?: () => void; }): symbol | Inject a panel into the composer. The send button calls onSubmit when clicked. While a panel is up, the composer is answering a QUESTION, not composing a message — so the affordances that lead nowhere go away with the text input: the attachment picker above all, which stayed clickable while the user was being asked something (“on voyait encore l’icône de upload”, reported from a real session). Ratified decision #8: an affordance nothing can honour is not rendered. What STAYS, deliberately: the attachments strip, because pending attachments are the user’s state and not an action to offer — hiding them would look like losing them; and the toolbar, because switching model still does something. The send button is the part the PANEL decides, through mode. 'submit' keeps it; 'none' says this panel has no act for it and it is not drawn. That third value is what a panel whose options settle on the first click needs — a single-choice question, an approval — and until it existed such a panel left a permanently disabled button beside options that never routed through it. Flip between them at any time with setPanelSubmitEnabled, which is how a panel that grows an act (an “Other…” field, a written instruction) turns the button back on. Declared with an attribute + CSS rather than the inline style.display this used to set on a child: an attribute is themeable, is visible to a consumer’s own rules, and does not clobber a display the consumer had set (the restore wrote '', not the previous value). Returns the TOKEN for this panel. Pass it back to hidePanel so a presenter that has already lost the slot cannot close the panel that replaced it, and supply onEvict to be told when that happens — an owner that is not told is an owner whose promise nobody can settle. |
hidePanel(token?: symbol): void | Remove the panel, tell its owner, and restore the composer’s own controls. With a token, this closes the panel only if that token still owns the slot — so a presenter settling late cannot tear down the panel that replaced it. With no token it closes whatever is there, which is what a consumer driving the composer directly means. BOTH forms notify. The no-token form used to call _teardownPanel directly, which nulls _panelOnEvict without ever calling it — so the documented public hidePanel() closed an open approval panel and left its request pending forever. The turn hung on “waiting for you”, and because AparteConfig.requestUserInput chains each request on the previous one, NO further question or approval on that config was ever presented again, for the life of the page. The old JSDoc justified the silent branch as “what reset() needs”. It was not: reset() calls _evictPanel(), which notifies. The branch had no consumer and one failure mode. The two forms differ on purpose, and the difference is who already knows: - With a matching token the OWNER is closing its own panel, which is what <aparte-elicitation>’s close() does right after it resolves. It must NOT be notified — telling it “you were evicted” for a request it just settled would fire onEvict against a finished promise. - With no token somebody else is closing a panel they do not own, so the owner cannot know and has to be told. That includes the presenter’s own defensive hidePanel(undefined) when it settles before showPanel ran: whatever is open then belongs to another request, and that request must not orphan. |
setPanelSubmitEnabled(enabled: boolean, mode?: AparteComposerPanelMode): void | Update the send button’s state while a panel is active. mode moves with it because both can change on the same event — opening the “Other…” field of a panel whose options settle on the click gives the button an act it did not have — and two separate calls would flash a wrong state between them. |
submit(): void | Submit the current value. Called by aparte-composer-send or programmatically. |
cancel(): void | Cancel the current streaming response. |
reset(): void | Reset the composer to its initial state. Clears value, attachments, and hides any active panel. Call this when switching conversations. |
focus(): void | Focus the input primitive inside this composer. |
Events
| Event | Type | Description |
|---|---|---|
aparte-send | CustomEvent<AparteSendEventDetail> | A message was submitted: the text, its attachments and the target. |
aparte-cancel | CustomEvent | The stop button was pressed. No detail; the two window events below carry the target. |
aparte-composer-change | CustomEvent<AparteComposerChangeEventDetail> | Any of value / streaming / disabled / attachments / panel changed, folded into one event. |
aparte-abort | CustomEvent<AparteAbortEventDetail> | Dispatched on window: stop the run for this target. |
aparte-message-aborted | CustomEvent<AparteMessageAbortedEventDetail> | The run for this target ended early — the user pressed Stop, or abort() was called. This element dispatches it on window; AparteClient also dispatches it on the chat host, so it is listenable on either. |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-composer-control-size | var(--aparte-btn-size-lg) | Width and height of the composer’s own control buttons (each is an .aparte-btn--icon, so it needs the opt-in .aparte-composer-row wrapper, which is what carries the size down) and the minimum height of the input’s editor, which needs no wrapper. One knob for the whole control set, so buttons stay aligned with a single line of text and anchored to the bottom once the input grows. It reaches the buttons by declaration, not by out-specifying them, so a panel mounted in the row keeps its own content’s sizing. |
--aparte-input-bg | var(--aparte-surface-1) | Background of the opt-in .aparte-composer-shell wrapper. |
--aparte-input-border | var(--aparte-border) | Border colour of that shell. Its :focus-within colour is --aparte-primary, a global token rather than a composer one. |
--aparte-radius-input | var(--aparte-radius-lg) | Corner radius of the shell, and of the dashed outline drawn while files are dragged over the composer. |
--aparte-message-max-width | 800px | Max width of the shell, which is margin: 0 auto at this width — the same width .aparte-message uses, so the composer keeps the transcript’s column. Set on THIS element it moves the shell only: custom properties inherit downward and the transcript is a sibling subtree, so set it on a shared ancestor (the chat host, :root) to move both. |
--aparte-viewport-padding | var(--aparte-space-8) | Inline gutter between this element and the chat’s edge — the same token the transcript reads, so the shell keeps .aparte-message’s column BELOW the max-width above as well as above it, where the two would otherwise diverge by the transcript’s whole inset. Only the inline halves are read here: the block axis is spaced by the viewport above and --aparte-chat-bottom-gap below. A container narrower than 520px tightens the transcript’s copy of this token but not the composer’s — a container query cannot reach this element, which is its own container root. |
<aparte-composer-input>
Section titled “<aparte-composer-input>”<aparte-composer-input> · class AparteComposerInput
Attributes
| Attribute | Description |
|---|---|
placeholder | Placeholder text (fallback: reads from aparte-composer) |
max-height | Max height in px before scroll (default: 200) |
min-height | Min height in px. When omitted, the stylesheet’s min-height governs (44px in aparte.css) — so themes can resize the editor in pure CSS without being fought by an inline height. |
disabled | Makes the field non-editable; the composer’s own disabled also reaches it. |
Methods
| Method | Description |
|---|---|
getValue(): string | The editor’s text, with <br> serialized back to newlines — textContent alone would collapse a multi-line draft onto a single line. |
setValue(value: string): void | Replace the editor’s content and mirror the value onto the parent composer. |
clear(): void | Empty the editor and mirror the empty value onto the parent composer. |
syncPlaceholder(): void | Re-read the placeholder fallback chain. Called by the composer when ITS placeholder changes — this element observes only its own attribute, and the composer’s is one of the three sources it falls back to. |
focus(): void | Focus the inner contenteditable rather than the host element. |
blur(): void | Blur the inner contenteditable rather than the host element. |
focusEnd(): void | Focus the editor and place the caret at the very end of its content. |
Events
| Event | Type | Description |
|---|---|---|
aparte-composer-submit | CustomEvent | A submitting Enter was pressed with no <aparte-composer> ancestor to submit to; with one it calls root.submit() and dispatches nothing. No detail — the host that placed this primitive reads getValue(). |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-composer-control-size | var(--aparte-btn-size-lg) | Single-line min-height of the editor. Inside the .aparte-composer-row layout helper the composer’s buttons read the same token, so one value resizes that whole control set and the row stays aligned. |
--aparte-input-padding-y | calc((var(--aparte-composer-control-size) - var(--aparte-input-line-height) * 1em) / 2) | Vertical padding inside the editor: half of what the control size leaves once one line is taken out (1lh where the browser has it). |
--aparte-input-padding-x | var(--aparte-space-6) | Horizontal padding inside the editor. |
--aparte-input-font-size | var(--aparte-font-size-base) | Editor font size. |
--aparte-input-line-height | var(--aparte-line-height-normal) | Editor line height — also what the auto-expand measures, so changing it changes the height the editor settles at (until max-height clamps it). Keep it unitless (1.5, not 24px): the editor’s padding reads it as a multiple of 1em where the browser has no lh unit. |
--aparte-text | — | Text and caret colour of the editor. |
--aparte-input-placeholder | — | Colour of the placeholder drawn by :empty::before (falls back to --aparte-text-muted). |
--aparte-input-bg | — | Field background, applied only when this input is the bubble’s inline editor (.aparte-message[data-editing]) — inside a composer the shell paints the surface instead. |
--aparte-input-border | — | Border colour of that same edit-mode box. |
--aparte-radius-input | var(--aparte-radius-lg) | Corner radius of the edit-mode box. |
--aparte-input-focus-border | — | Border colour of the edit-mode box while it holds focus (:focus-within). |
<aparte-composer-send>
Section titled “<aparte-composer-send>”<aparte-composer-send> · class AparteComposerSend
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-composer-control-size | var(--aparte-btn-size-lg) | Width/height of the button inside the .aparte-composer-row layout helper, shared with the input’s single-line height so the row stays aligned. It wins over --aparte-send-btn-size there. |
--aparte-send-btn-size | var(--aparte-btn-size-lg) | Width/height of the button outside that row helper. On coarse pointers it is raised to --aparte-touch-target-size. |
--aparte-touch-target-size | 44px | Hit-area floor applied to the button under @media (pointer: coarse). |
--aparte-radius-send-btn | var(--aparte-radius-md) | Corner radius of the button. |
--aparte-primary | — | Button background. |
--aparte-primary-hover | — | Button background on hover, while enabled. |
--aparte-on-primary | — | The glyph’s colour. Undeclared by default, which means the recipe derives it from --aparte-primary itself, so a theme that changes the fill gets a readable glyph with no second edit. Declare it to choose one — it then applies to every primary control, which is the honest scope. |
--aparte-ink-flip | 0.57 | Fill lightness at which the derived ink flips from dark to light, for every solid control. |
--aparte-ink-dark | 0.176 | How dark that derived ink goes. Not 0: at zero lightness OKLCH drops the chroma, and the ink loses the fill’s own hue. |
--aparte-send-disabled-bg | — | Background while disabled (falls back to --aparte-primary, which is then dimmed by opacity). |
<aparte-composer-cancel>
Section titled “<aparte-composer-cancel>”<aparte-composer-cancel> · class AparteComposerCancel
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-composer-control-size | var(--aparte-btn-size-lg) | Width/height of the button inside the .aparte-composer-row layout helper, shared with the composer’s other controls so the row stays aligned. |
--aparte-radius-action-btn | var(--aparte-radius-sm) | Corner radius of the button. |
--aparte-neutral | — | Icon colour at rest (the button’s background is transparent). |
--aparte-text | — | Icon colour on hover. |
--aparte-surface-2 | — | Button background on hover. |
<aparte-composer-action>
Section titled “<aparte-composer-action>”<aparte-composer-action> · class AparteComposerAction
Attributes
| Attribute | Description |
|---|---|
icon | Icon key for aparteGlobalConfig.getIcon(), or raw SVG/HTML starting with <. A key core’s glyph set does not hold renders AS TEXT (icon="star" draws the word star), so bring your own set through setIconProvider or pass the SVG. |
label | Accessible label (also used as tooltip) |
disabled | Disables the button |
action-id | Identifies WHICH button fired; carried as AparteActionClickEventDetail.actionId. Read lazily at dispatch time rather than observed, so changing it takes effect on the next click. |
Events
| Event | Type | Description |
|---|---|---|
aparte-action-click | CustomEvent<AparteActionClickEventDetail> | Bubbles up when the button is clicked, carrying which button it was and the composer it belongs to. The type argument is not decoration: a BARE @fires records CustomEvent with no argument, and the bindings generator then emits EventEmitter<void> with a listener that drops $event — so an Angular consumer with two custom buttons could not tell which one fired. detail: { actionId: string, composer: AparteComposer | null } |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-input-action-btn-size | var(--aparte-btn-size-lg) | Square size of the button. On a coarse pointer the stylesheet re-sets it to --aparte-touch-target-size (44px) on .aparte-action-button itself, which wins over a value inherited from your theme. |
--aparte-input-action-btn-icon-size | 20px | Size of the <svg> inside it. |
--aparte-radius-action-btn | var(--aparte-radius-sm) | Corner radius. |
<aparte-composer-add-attachment>
Section titled “<aparte-composer-add-attachment>”<aparte-composer-add-attachment> · class AparteComposerAddAttachment
Attributes
| Attribute | Description |
|---|---|
accept | MIME types / extensions passed to the file input (e.g. “image/*,.pdf”) |
multiple | Allow multiple file selection (default: true) |
disabled | Greys out the picker. Drops are gated by the composer root’s disabled, not by this one. |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-input-action-btn-size | var(--aparte-btn-size-lg) | Square size of the button. On a coarse pointer the stylesheet re-sets it to --aparte-touch-target-size (44px) on .aparte-action-button itself, which wins over a value inherited from your theme. |
--aparte-input-action-btn-icon-size | 20px | Size of the <svg> inside it. |
--aparte-radius-action-btn | var(--aparte-radius-sm) | Corner radius. |
<aparte-composer-attachments>
Section titled “<aparte-composer-attachments>”<aparte-composer-attachments> · class AparteComposerAttachments
Events
| Event | Type | Description |
|---|---|---|
aparte-attachment-preview | CustomEvent<AparteAttachmentPreviewEventDetail> | An attached image was clicked; the app opens it full-size, and only if it declared attachmentPreview. |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-attachments-max-height | 140px | Height cap on the strip; past it the tiles scroll instead of pushing the composer up. |
--aparte-attachment-image-size | 72px | Tile edge. The stylesheet sets 56px on this element (the :root default is 72px, and the sent-message strip re-sets 40px on itself), so a theme-level value reaches neither strip — target aparte-composer-attachments to resize these tiles. |
--aparte-thumb-radius | var(--aparte-radius-lg) | Tile corner radius. |
--aparte-attachment-chip-bg | var(--aparte-surface-2) | Tile background, seen behind a non-image file. |
--aparte-attachment-chip-border | var(--aparte-border) | Tile border colour. |
--aparte-thumb-name-color | #ffffff | Filename colour on the hover overlay. |
--aparte-thumb-name-scrim | linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0)) | Background behind the filename; a bottom-up black gradient by default, so the name stays legible over any picture. |
--aparte-thumb-name-padding | 14px 5px 4px | Padding of that overlay. |
--aparte-thumb-remove-size | 18px | Diameter of the ✗ button. |
--aparte-thumb-remove-inset | 3px | Its inset from the tile’s top and right edges (physical right, so it does not flip in a right-to-left locale). |
--aparte-thumb-remove-bg | rgba(0, 0, 0, 0.6) | Its background. |
--aparte-thumb-remove-bg-hover | rgba(0, 0, 0, 0.85) | Its hover background. |
--aparte-thumb-remove-color | #ffffff | Its glyph colour. |
<aparte-composer-toolbar>
Section titled “<aparte-composer-toolbar>”<aparte-composer-toolbar> · class AparteComposerToolbar
Attributes
| Attribute | Description |
|---|---|
data-empty | Reflected BY the element while it holds neither a visible element child nor non-whitespace text; the stylesheet hides it then. A child that hides itself — hidden, or its own data-empty the way <aparte-context> does before its first usage — does not count: a row whose only child shows nothing is an empty band with a border. Read-only, do not set it yourself. |
Accessibility
Section titled “Accessibility”- The field is a multi-line
textbox, named by its placeholder. - The send button’s name follows what it does: Send, Stop while a reply streams, Submit when a
question panel is open. All three are locale keys (
sendButton,stopButton,submitButton). - Enter never sends in the middle of an IME composition, so confirming a Chinese or Japanese candidate does not send the message.
Related
Section titled “Related”- Chat — the composer inside a full conversation.
- Suggestions — starters that fill the composer.
- Model selector — the picker for the toolbar.