# V combobox 20

> Font picker grouped by category with live preview sentence

A shadcn/ui block from the [@cnippet](https://ui.cnippet.dev/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet/v-combobox-20).

- Page, with a live preview: https://shadcn.exchange/r/cnippet/v-combobox-20
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/cnippet/v-combobox-20-light-eaa4fbd0ea80844f.png
- Registry item: https://ui.cnippet.dev/r/v-combobox-20.json
- Categories: combobox
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.cnippet.dev/r/v-combobox-20.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@cnippet/combobox`

## Files

### `registry/default/variants/v-combobox-20.tsx`

```tsx
"use client";

import { ChevronsUpDownIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/registry/default/ui/button";
import {
  Combobox,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxSeparator,
  ComboboxTrigger,
  ComboboxValue,
} from "@/registry/default/ui/combobox";

interface Font {
  label: string;
  value: string;
  category: "sans" | "serif" | "mono";
}

const fonts: Font[] = [
  { category: "sans", label: "Inter", value: "inter" },
  { category: "sans", label: "Geist", value: "geist" },
  { category: "sans", label: "DM Sans", value: "dm-sans" },
  { category: "serif", label: "Lora", value: "lora" },
  { category: "serif", label: "Playfair Display", value: "playfair" },
  { category: "mono", label: "Geist Mono", value: "geist-mono" },
  { category: "mono", label: "JetBrains Mono", value: "jetbrains" },
  { category: "mono", label: "Fira Code", value: "fira-code" },
];

const groups = [
  {
    id: "sans",
    items: fonts.filter((f) => f.category === "sans"),
    label: "Sans-serif",
  },
  {
    id: "serif",
    items: fonts.filter((f) => f.category === "serif"),
    label: "Serif",
  },
  {
    id: "mono",
    items: fonts.filter((f) => f.category === "mono"),
    label: "Monospace",
  },
];

export default function Particle() {
  const [value, setValue] = useState<Font | null>(fonts[0] ?? null);

  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <Combobox
        items={fonts}
        onValueChange={(v) => setValue(v as Font | null)}
        value={value}
      >
        <ComboboxTrigger
          render={
            <Button
              className="w-full justify-between font-normal"
              variant="outline"
            />
          }
        >
          <ComboboxValue placeholder="Select font..." />
          <ChevronsUpDownIcon className="-me-1!" />
        </ComboboxTrigger>
        <ComboboxPopup>
          <ComboboxInput placeholder="Search fonts..." showTrigger={false} />
          <ComboboxEmpty>No fonts found.</ComboboxEmpty>
          <ComboboxList>
            {groups.map((group, i) => (
              <ComboboxGroup key={group.id}>
                {i > 0 && <ComboboxSeparator />}
                <ComboboxGroupLabel>{group.label}</ComboboxGroupLabel>
                {group.items.map((font) => (
                  <ComboboxItem key={font.value} value={font}>
                    <span
                      className={
                        font.category === "mono"
                          ? "font-mono text-sm"
                          : "text-sm"
                      }
                    >
                      {font.label}
                    </span>
                  </ComboboxItem>
                ))}
              </ComboboxGroup>
            ))}
          </ComboboxList>
        </ComboboxPopup>
      </Combobox>
      {value && (
        <p
          className={`text-muted-foreground text-sm ${value.category === "mono" ? "font-mono" : ""}`}
        >
          The quick brown fox jumps over the lazy dog.
        </p>
      )}
    </div>
  );
}
```
