# Combobox

A combobox with filtered list and panel animations.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).

## Installation

<TabsTrigger value="cli">Command</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>

```bash
npx shadcn@latest add @framecn/combobox
```

<Step>Copy and paste the following code into your project.</Step>

<Step>Update the import paths to match your project setup.</Step>

## Usage

```tsx
import { Combobox } from "@/components/ui/combobox";
```

```tsx
<div style={{ position: "relative", width: "100%", height: "100%" }}>
  <Combobox state="opened" query="ba" />
</div>
```

Wrap in a positioned container — the panel anchors below the trigger.

## Smooth transitions

The docs preview opens the panel, types `"ba"` to filter the list, animates a row hover → press → selected, then closes. Drive the panel with `useComboboxTransition`, typing with `revealCount` from `@/lib/framecn-ui`, and row states with `useSelectItemTransition`:

```tsx
import { useCurrentFrame, revealCount } from "@/lib/framecn-ui";
import { Combobox } from "@/components/framecn/combobox";
import { useComboboxTransition } from "@/components/framecn/use-combobox-transition";
import { useSelectItemTransition } from "@/components/framecn/use-select-item-transition";

const QUERY = "ba";
const TYPE_START = 32;

export const Scene = () => {
  const frame = useCurrentFrame();

  const panelStyle = useComboboxTransition([
    { at: 16, state: "opened", duration: 12 },
    { at: 100, state: "closed", duration: 12 },
  ]);

  const revealed = revealCount(
    Math.max(0, frame - TYPE_START),
    30,
    QUERY.length,
    4
  );

  const itemStyle = useSelectItemTransition([
    { at: 60, state: "hover", duration: 8 },
    { at: 72, state: "press", duration: 6 },
    { at: 80, state: "selected", duration: 8 },
  ]);

  return (
    <div style={{ position: "relative", width: "100%", height: "100%" }}>
      <Combobox
        style={panelStyle}
        query={QUERY}
        revealCount={revealed}
        itemStyles={[itemStyle]}
        placeholder="Select a fruit…"
      />
    </div>
  );
};
```

`style` takes precedence over `state` when both are provided. `itemStyles` is indexed into the **filtered** list.

## API Reference

### Combobox

| Prop               | Type                               | Default                                  |
| ------------------ | ---------------------------------- | ---------------------------------------- |
| `state`            | `"opened" \| "closed"`             | `"closed"`                               |
| `style`            | `ComboboxStyle`                    | `-` (takes precedence over `state`)      |
| `query`            | `string`                           | `""`                                     |
| `revealCount`      | `number`                           | `-` (full `query` shown when omitted)    |
| `placeholder`      | `string`                           | `"Select a fruit…"`                      |
| `items`            | `string[]`                         | `["Apple", "Banana", "Orange", "Grape"]` |
| `selectedIndex`    | `number`                           | `-1`                                     |
| `highlightedIndex` | `number`                           | `-1`                                     |
| `pressedIndex`     | `number`                           | `-1`                                     |
| `itemStyles`       | `(SelectItemStyle \| undefined)[]` | `-` (indexed into filtered list)         |
| `inputStyle`       | `InputStyle`                       | `-`                                      |
| `theme`            | `Partial<FramecnTheme>`            | `-`                                      |
| `className`        | `string`                           | `-`                                      |