Image picker

A grid of selectable image tiles. Single-select — the chosen tile gets a checkmark badge and a ring border. Supports disabled, error, and configurable column count.

*

Choose your preferred layout

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/image-picker.json
Usage
Pass an items array and a controlled value/onChange pair.
TSImport
import { ImagePicker, type ImagePickerItem } from "@/components/blocks/image-picker";
TSExample
"use client";

import { useState } from "react";
import { ImagePicker, type ImagePickerItem } from "@/components/blocks/image-picker";

const items: ImagePickerItem[] = [
  { id: "a", src: "/images/layout-a.jpg", alt: "Layout A", caption: "Layout A" },
  { id: "b", src: "/images/layout-b.jpg", alt: "Layout B", caption: "Layout B" },
  { id: "c", src: "/images/layout-c.jpg", alt: "Layout C", caption: "Layout C" },
];

export function Example() {
  const [value, setValue] = useState("");
  return (
    <ImagePicker
      label="Pick a layout"
      description="Choose your preferred layout"
      required
      columns={3}
      items={items}
      value={value}
      onChange={setValue}
    />
  );
}
Composition
Single component with label, grid, and error slot.
ImagePicker                        (single component — prop-driven grid of image tiles)
├── label / description / required  (field header)
├── columns                         (grid column count, default 3)
├── items                           (ImagePickerItem[]: id + src + alt + optional caption)
├── value / onChange                (controlled selection — value is the selected item id)
└── error                           (red border + red error text below the grid)

Each tile is a <button role="radio"> with a checkmark badge when selected.
Default
Three-column grid with label, description, and required marker.
*

Choose your preferred layout

Two columns
Pass `columns={2}` for a wider tile layout — good for portrait vs landscape orientation pickers.
With description
Add a `description` to each item to show a helper line below the caption — useful when the caption alone isn't enough context.

Each option includes a brief explainer.

Error state
Pass an `error` string to show a red message below the grid and highlight unselected tiles.
*
Multi-select
Pass `multiple` to let users pick more than one tile. Each tile gets a `+` badge in the top-right that flips to a checkmark when selected — same pattern as the Multi-select cards radio variant.

Tap the + on any tile to add it. Tap again to remove.

Multi-select with dictation notes
Pass `withNotes` (alongside `multiple`) to reveal a textarea with browser dictation under every selected tile, so users can speak what they like about each one.

Pick the layouts you like and dictate why — tap the mic in the textarea.

Markdown content tiles
Omit `src` and pass a `content` markdown string on each item to render long-form text instead of an image. Supports paragraphs, **bold**, *italic*, lists, and inline `code`. Pairs well with `columns={1}` for a stacked layout.
*
Markdown content tiles — multi-select
Same long-form markdown tiles, but with `multiple` so users can pick more than one. The `+` badge flips to a checkmark when selected.
*

Tap the + on any option to add it. Tap again to remove.

API Reference
Props for ImagePicker and ImagePickerItem.

ImagePicker

PropTypeDefaultDescription
itemsImagePickerItem[]Array of image tiles to display.
multiplebooleanfalseAllow multiple tiles to be selected. When true, `value` is `string[]`.
valuestring | string[]Controlled selection — string when single, string[] when `multiple`.
onChange(value: string | string[]) => voidFires with the next selection.
withNotesbooleanfalseWhen a tile is selected, reveal a textarea (with browser dictation) for the user to describe what they like. Best with `multiple`.
notesRecord<string, string>Controlled notes map keyed by item id.
onNotesChange(notes: Record<string, string>) => voidFires when any selected tile's note changes.
notesPlaceholderstring"What do you like about this image?"Placeholder for the per-tile notes textarea.
columnsnumber3Number of columns in the grid.
labelstringField label rendered above the grid.
descriptionstringHelper text below the label.
requiredbooleanAdds a red asterisk to the label.
disabledbooleanGrays out the grid and prevents interaction.
errorstringError message shown below the grid.

ImagePickerItem

PropTypeDefaultDescription
idstringUnique identifier — used as the value when selected.
srcstringImage URL. Omit when passing `content` to render text/markdown instead.
altstringAlt text for the image.
captionstringOptional caption rendered below the image inside the tile.
descriptionstringOptional helper text rendered below the caption — shows on each tile.
contentstringMarkdown string rendered in place of the image — supports paragraphs, **bold**, *italic*, lists, and inline `code`. Pairs well with `columns={1}` for stacked, text-heavy tiles.