ComponentsAsyncSelect
AsyncSelect
Let users choose from options loaded by your app, with loading and retry states.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import { AsyncSelect } from "@dethink/components";Type to change the app-owned query; the examples filter local arrays to model a server result window.
Basic
Single-value search where the app controls the query and result list.
Show sourceexamples/async-select/basic.tsx
"use client";
import { useMemo, useState } from "react";
import { AsyncSelect } from "@dethink/components";
const accountItems = [
{ label: "Acme Operations", value: "acme" },
{ label: "Dethink Labs", value: "dethink" },
{ label: "Northstar Systems", value: "northstar" },
{ label: "Signal Foundry", value: "signal" },
];
export function AsyncSelectBasic() {
const [query, setQuery] = useState("");
const items = useMemo(
() =>
accountItems.filter((item) =>
item.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
),
[query],
);
return (
<div className="mx-auto max-w-sm">
<AsyncSelect
inputValue={query}
items={items}
label="Account"
name="account"
onInputValueChange={setQuery}
placeholder="Search accounts"
/>
</div>
);
}Multiple values
Multiple mode keeps selected chip labels visible even when the current result window changes.
Selected labels stay visible while server results change.
Show sourceexamples/async-select/multiple.tsx
"use client";
import { useState } from "react";
import { AsyncSelect, FieldDescription, Stack } from "@dethink/components";
const ownerItems = [
{ label: "Ari Chen", value: "ari" },
{ label: "Mira Patel", value: "mira" },
{ label: "Noah Smith", value: "noah" },
{ label: "Sana Iqbal", value: "sana" },
];
export function AsyncSelectMultiple() {
const [query, setQuery] = useState("");
const [value, setValue] = useState<string[]>(["mira"]);
const items = ownerItems.filter((item) =>
item.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
);
return (
<div className="mx-auto max-w-sm">
<Stack gap="3">
<AsyncSelect
selectionMode="multiple"
inputValue={query}
items={items}
label="Owners"
name="owners"
onInputValueChange={setQuery}
onValueChange={(nextValue) => setValue(nextValue as string[])}
selectedItems={ownerItems.filter((item) =>
value.includes(item.value),
)}
value={value}
/>
<FieldDescription>
Selected labels stay visible while server results change.
</FieldDescription>
</Stack>
</div>
);
}Async states
Loading, empty, error-with-retry, invalid, required, read-only, and disabled are explicit props.
Show sourceexamples/async-select/states.tsx
"use client";
import { AsyncSelect } from "@dethink/components";
export function AsyncSelectStates() {
return (
<div className="space-y-4">
<div className="grid gap-4 md:grid-cols-3">
<AsyncSelect
inputValue="ac"
items={[]}
label="Loading customer"
loading
loadingMessage="Finding customers..."
/>
<AsyncSelect
inputValue="missing"
items={[]}
label="Empty customer"
emptyMessage="No customer found."
/>
<AsyncSelect
error="Customer lookup failed."
inputValue="acme"
items={[]}
label="Errored customer"
onRetry={() => undefined}
retryLabel="Try again"
/>
</div>
<div className="grid gap-4 md:grid-cols-3">
<AsyncSelect
errorMessage="Choose an account before saving."
invalid
items={[]}
label="Required account"
required
/>
<AsyncSelect
readOnly
defaultValue="acme"
items={[{ label: "Acme Operations", value: "acme" }]}
label="Inherited account"
/>
<AsyncSelect
disabled
defaultValue="dethink"
items={[{ label: "Dethink Labs", value: "dethink" }]}
label="Locked account"
/>
</div>
</div>
);
}Theme, RTL, and wrapping
Multiple async selection using compact dark tokens, RTL direction, and narrow chip wrapping.
Show sourceexamples/async-select/theme-and-wrapping.tsx
"use client";
import { AsyncSelect, DethinkProvider } from "@dethink/components";
const ownerItems = [
{ label: "Ari Chen", value: "ari" },
{ label: "Mira Patel", value: "mira" },
{ label: "Noah Smith", value: "noah" },
{ label: "Sana Iqbal", value: "sana" },
];
export function AsyncSelectThemeAndWrapping() {
return (
<DethinkProvider theme="dark" density="compact" dir="rtl">
<div className="border-border bg-background mx-auto max-w-72 rounded-lg border p-4">
<AsyncSelect
selectionMode="multiple"
defaultValue={["ari", "mira", "sana"] as string[]}
items={ownerItems}
label="مالكو الحساب"
name="accountOwners"
/>
</div>
</DethinkProvider>
);
}Examples that combine components for common tasks.
Server filter
A form-ready server lookup with min-query guidance and a second field using a stable result.
Show sourceexamples/async-select/recipe-server-filter.tsx
"use client";
import { useMemo, useState } from "react";
import { AsyncSelect, Form, Stack } from "@dethink/components";
const modelItems = [
{ label: "Fast summarizer", value: "fast-summarizer" },
{ label: "Reasoning planner", value: "reasoning-planner" },
{ label: "Vision analyst", value: "vision-analyst" },
{ label: "Customer support bot", value: "support-bot" },
];
export function AsyncSelectRecipeServerFilter() {
const [query, setQuery] = useState("");
const items = useMemo(
() =>
query.trim().length < 3
? []
: modelItems.filter((item) =>
item.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
),
[query],
);
return (
<Form action="/models" method="get" className="mx-auto max-w-sm">
<Stack gap="4">
<AsyncSelect
inputValue={query}
items={items}
label="Model"
minQueryLength={3}
minQueryMessage="Type three characters before querying the server."
name="model"
onInputValueChange={setQuery}
placeholder="Search models"
/>
<AsyncSelect
inputValue="ari"
items={[{ label: "Ari Chen", value: "ari" }]}
label="Fallback owner"
name="fallbackOwner"
/>
</Stack>
</Form>
);
}AsyncSelect accepts a shared item shape: { value, label?, textValue? }.
| Prop | What it does | Default |
|---|---|---|
selectionMode"single" | "multiple" | Whether the component renders Combobox or MultiSelect behavior. | "single" |
value / defaultValue / onValueChangestring | null | string[] | Controlled or uncontrolled selected value shape. | Not set |
inputValue / defaultInputValue / onInputValueChangestring / string / (text) => void | App-owned query state. AsyncSelect never fetches; your app owns the server call. | "" |
items / selectedItemsIterable<AsyncSelectItemData> | Current result window and optional selected item data for stable labels. | Not set |
loading / error / onRetryboolean / ReactNode / () => void | Explicit async status flags and retry action. | false / — / — |
emptyMessage / loadingMessage / minQueryMessageReactNode | Messages rendered in the status region. | built-in copy |
minQueryLengthnumber | Minimum query length before results should be shown. | 0 |
disabledKeysIterable<string> | Result values that cannot be selected. | Not set |
label / description / errorMessageReactNode | The field label, help text, and error message. | Not set |
namestring | Hidden input name for native forms; multiple mode submits repeated values. | Not set |