<!-- import '@aparte/plugin-model-selector' registers the element; it lists the providers
your config knows, one group per provider. -->
<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>
<aparte-composer-toolbar>
<aparte-model-selector persist searchable></aparte-model-selector>
</aparte-composer-toolbar>
</div>
</aparte-composer>
<script>
const { aparteGlobalConfig } = window.aparte;
const { createScenarioProvider } = window.apartePlugins.scenario;
// Stand-ins for real providers (openai-compat, ai-sdk, transformers): only their model lists matter here.
aparteGlobalConfig.registerAIProvider(
createScenarioProvider({ id: 'cloud', name: 'Cloud', models: [{ id: 'large', name: 'Large' }, { id: 'fast', name: 'Fast' }] }),
createScenarioProvider({ id: 'local', name: 'In the browser', models: [{ id: 'small', name: 'Small, offline' }] }),
);
aparteGlobalConfig.setModelConfig({ defaultProvider: 'cloud', defaultModel: 'large' });
</script><aparte-model-selector> lists the providers your config knows, one group per provider, with
the models each one fetches; a provider with no list to fetch, such as the Vercel AI SDK bridge,
is left out. With a single provider it is a flat list. With persist, the choice is what the
next message is sent to: the pick is written into the config. Without it, the element only sends
aparte-model-change, for code of yours to act on.
It is built on core’s select, and it reads the config of the chat it sits in, so two chats on one page each list their own models.
npm install @aparte/plugin-model-selector @aparte/coreimport '@aparte/plugin-model-selector'; // registers the elementIts place is the composer’s toolbar. margin-inline-start: auto pushes it to the end of the
row; the four framework wrappers take it through their toolbar slot (see
the composer toolbar).
<aparte-composer-toolbar>
<aparte-model-selector auto-select persist searchable style="margin-inline-start: auto"></aparte-model-selector>
</aparte-composer-toolbar>The plugin types its own element, one subpath per framework, so the tag is checked as soon as you import that subpath once:
Nothing to import: the tag is in HTMLElementTagNameMap once the plugin is imported, and
aparte-model-change is typed through HTMLElementEventMap.
<aparte-model-selector persist searchable placeholder="Pick a model"></aparte-model-selector>import '@aparte/plugin-model-selector/react';<aparte-model-selector persist="" searchable="" placeholder="Pick a model" />import '@aparte/plugin-model-selector/vue';<aparte-model-selector persist="" searchable="" placeholder="Pick a model" />import '@aparte/plugin-model-selector/svelte';<aparte-model-selector persist="" searchable="" placeholder="Pick a model" />A directive, because Angular’s template compiler needs a class claiming the selector to turn
[persist] into a real binding.
import { AparteModelSelectorDirective } from '@aparte/plugin-model-selector/angular';
// @Component({ imports: [AparteModelSelectorDirective], … })<aparte-model-selector [persist]="true" [searchable]="true" (modelChange)="use($event.modelId)" />Outside Angular, a presence attribute is '' to set and null to remove, never false
(the rule). The
framework packages are optional peer dependencies: you carry only the one you use.
Customize
Section titled “Customize”The list loads asynchronously, so for a moment the chat can be mounted with no model chosen. To keep the composer from sending until one is:
import { aparteGlobalConfig } from '@aparte/core';
aparteGlobalConfig.setRequireModelSelection(true);The composer greys out and comes back once auto-select, or the reader, picks a model.
<aparte-model-selector> · class AparteModelSelector
Attributes
| Attribute | Description |
|---|---|
persist | Writes the selection back through the config, so it survives a reload. |
auto-select | Selects the first model as soon as one is available. |
searchable | Adds the dropdown’s filter field. |
placeholder | Overrides the text shown while nothing is selected. |
disabled | Disables the picker. Inside an <aparte-composer> it also follows the composer’s own disabled, so a disabled composer does not leave its model picker operable. |
Properties
| Property | Type | Description |
|---|---|---|
providerId (readonly) | string | null | Current provider ID. |
modelId (readonly) | string | null | Current model ID. |
Methods
| Method | Description |
|---|---|
aparteConfigChanged(next: AparteConfig): void | The boundary above us appeared or changed: move the subscription and reload, because a different config means different providers and a different selection. See AparteConfigAware. The element’s own per-instance tests passed before this existed only because they mount the boundary FIRST — the one ordering no wrapper produces. |
setSelection(providerId: string, modelId: string): void | Programmatically set the selection. |
Events
| Event | Type | Description |
|---|---|---|
aparte-model-change | CustomEvent<AparteModelChangeEventDetail> | The provider or the model changed; carries both ids. |
Accessibility
Section titled “Accessibility”- The button is named by its placeholder and the current model, and opens a list box;
searchableadds a filter field. - Providers are option groups, so a screen reader names the provider of each model.
Related
Section titled “Related”- Approval mode — the other switch that lives in the toolbar.
- Composer — the toolbar it sits in.
- Select — the control it is built on.