Components
SmartSuggestion
Combobox with AI-generated dropdown suggestions, keyboard nav, and full ARIA.
An input + listbox combobox that streams suggestions from your SmartClient. Arrow keys to navigate, Enter to pick, Esc to close. Implements the ARIA 1.2 combobox pattern.
Preview
import { useState } from 'react';import { SmartProvider, SmartSuggestion } from '@extedcoud/smart-components';import { createMockClient } from '@extedcoud/smart-components/adapters/mock';const client = createMockClient({complete: async () => ['Apple', 'Apricot', 'Banana', 'Blueberry'].join('\n'),});export function Example() {const [value, setValue] = useState('');return ( <SmartProvider client={client}> <SmartSuggestion value={value} onChange={setValue} onSelect={(item) => console.log('picked', item)} placeholder="Type to filter…" /> </SmartProvider>);}Styled example — command palette
A ⌘K-style command palette with icons, kbd hints, gradient selection, and a status footer.
↑↓ to navigate · ↵ to select · esc to dismiss10 actions
'use client';import { useState } from 'react';import { SmartProvider, SmartSuggestion } from '@extedcoud/smart-components';import { makeSuggestionMock } from '@/lib/mock-client';const COMMANDS = [ 'Open recent file', 'Open settings', 'Run build', 'Run tests', 'Toggle theme', 'Create new component', 'Create new hook', 'Search in workspace', 'Show keyboard shortcuts', 'Sign out',];const ICONS: Record<string, string> = { Open: '📂', Run: '⚡', Toggle: '🌓', Create: '✨', Search: '🔍', Show: '⌨️', Sign: '🚪',};function iconFor(label: string) { const first = label.split(' ')[0]; return ICONS[first] ?? '·';}const client = makeSuggestionMock(COMMANDS, 180);export default function SmartSuggestionStylized() { const [value, setValue] = useState(''); return ( <SmartProvider client={client}> <div className="w-full max-w-md"> <div className="overflow-hidden rounded-xl border border-fd-border bg-fd-card shadow-xl shadow-fd-foreground/5"> <div className="flex items-center gap-2 border-b border-fd-border px-3 py-2 text-fd-muted-foreground"> <span aria-hidden>⌘</span> <SmartSuggestion value={value} onChange={setValue} placeholder="Type a command…" wrapperClassName="relative flex-1" className="w-full bg-transparent text-base text-fd-foreground outline-none placeholder:text-fd-muted-foreground" listClassName="absolute left-0 right-0 top-full z-10 mt-2 max-h-72 overflow-y-auto rounded-lg border border-fd-border bg-fd-card p-1 shadow-xl" itemClassName="flex items-center gap-3 rounded-md px-3 py-2 text-sm text-fd-foreground transition aria-selected:bg-gradient-to-r aria-selected:from-indigo-500/15 aria-selected:to-fuchsia-500/15 aria-selected:text-fd-foreground hover:bg-fd-accent cursor-pointer" renderItem={(item, { active }) => ( <> <span className="text-lg" aria-hidden> {iconFor(item)} </span> <span className="flex-1">{item}</span> {active && ( <kbd className="rounded border border-fd-border bg-fd-background px-1.5 py-0.5 text-[10px] font-mono text-fd-muted-foreground"> ↵ </kbd> )} </> )} /> <kbd className="hidden rounded border border-fd-border bg-fd-background px-1.5 py-0.5 text-[10px] font-mono text-fd-muted-foreground sm:block"> ⌘ K </kbd> </div> <div className="flex items-center justify-between bg-fd-secondary/30 px-3 py-1.5 text-[11px] text-fd-muted-foreground"> <span>↑↓ to navigate · ↵ to select · esc to dismiss</span> <span>{COMMANDS.length} actions</span> </div> </div> </div> </SmartProvider> );}Custom item renderer
<SmartSuggestion
value={v}
onChange={setV}
renderItem={(item, { active }) => (
<span style={{ fontWeight: active ? 700 : 400 }}>
<SparkleIcon /> {item}
</span>
)}
/>Empty + loading states
<SmartSuggestion
value={v}
onChange={setV}
renderEmpty={() => <em>No matches</em>}
renderLoading={() => <span>Thinking…</span>}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | — | Controlled input value. |
| onChange* | (value: string) => void | — | Called on user input. |
| onSelect | (item: string) => void | — | Called when the user selects a suggestion (Enter, click, or keyboard). |
| context | string | — | Optional context string included in the prompt. |
| count | number | 5 | Max suggestions requested. |
| debounceMs | number | 300 | Debounce window in ms. |
| minChars | number | 1 | Minimum chars before suggestions fetch. |
| disableAI | boolean | false | Disable AI calls. |
| renderItem | (item, { active, index }) => ReactNode | — | Render override for each item. |
| renderEmpty | () => ReactNode | — | Render override when results are empty. |
| renderLoading | () => ReactNode | — | Render override while loading. |
| wrapperClassName | string | — | Class name on the outer wrapper. |
| listClassName | string | — | Class name on the listbox. |
| itemClassName | string | — | Class name on each item. |
| ...rest | InputHTMLAttributes<HTMLInputElement> | — | All native input props (combobox aria-* and onSelect are managed internally). |
Mobile notes
- Pointer events are used for selection — touch + mouse + pen unified.
- Render items with
min-height: 44pxto meet WCAG touch-target minimum. - Known limitation: soft-keyboard overlap on narrow viewports. For phone-sized layouts, hoist the list via a portal or render a fullscreen picker.