Audience selection

Searchable card grid for picking a project's primary audience, with an add button that creates a new audience in a drawer or modal.

*

Please select the most appropriate category below or create a new one for your design.

onChange valuenone
Installation
$Terminal
npx shadcn add https://raw.githubusercontent.com/sis-thesqd/squad-sdk/main/public/r/audience-selection-block.json
Usage
Drop in the demo component or wire up the picker with your own state.
TSImport
import { AudienceSelection, type Audience } from "@/components/blocks/audience-selection-block";
TSExample
"use client";

import * as React from "react";
import {
  AudienceSelection,
  type Audience,
} from "@/components/blocks/audience-selection-block";

const AUDIENCES: Audience[] = [
  { id: "a-teens", name: "The Teens", category: "Teenagers" },
  { id: "a-kids", name: "Kids", category: "Kid-Friendly" },
  { id: "a-women", name: "Women's Ministry", category: "Women Only" },
  { id: "a-young", name: "Young Adults", category: "General Audience" },
];

export default function Page() {
  // `audiences` is the list to choose from. Creating one via the add button
  // appends to an internal copy (and fires `onCreate` if provided), so the
  // picker is self-contained without a backing store.
  //
  // `value` + `onChange` control the selected audience id; omit both to let
  // the picker manage its own selection.
  //
  // Flat props: `title`, `caption`, `required`, `showAddButton`,
  // `searchEnabled`, `cols` (number | "auto"), `addLayout`
  // ("drawer" | "modal"), `categories`, `addButtonLabel`, `onCreate`,
  // and `error`.
  const [value, setValue] = React.useState<string>();
  return (
    <AudienceSelection
      title="Who will be the primary audience for this project?"
      caption="Please select the most appropriate category below or create a new one."
      required
      audiences={AUDIENCES}
      cols={2}
      addLayout="drawer"
      value={value}
      onChange={setValue}
    />
  );
}
Composition
Anatomy of the audience selection block.
AudienceSelection (flat block)
├── FieldLabel (title + caption + required asterisk)
├── Toolbar row: Search Input (searchEnabled) + add icon-button (showAddButton)
├── RadioGroup type="card" (cols | "auto") — 3 rows, then Pagination
│   └── per audience: category icon + name (label) + category Badge + ⋯ menu (editable)
│       └── DropdownMenu → Edit (reuses the form) / Delete (destructive)
├── Create / edit form (addLayout="drawer" | "modal")
│   ├── TextField — audience name (≥2 chars, de-duped)
│   └── RadioGroup type="chip" — category
└── Delete confirmation Dialog (type="confirm")
Default
A searchable grid of audience cards (category icon + name + category badge) backed by the SDK `RadioGroup`, capped at 3 rows with pagination. `title` / `caption` render the field label via `FieldLabel`; the add button opens a create form in a side drawer; and each card's ⋯ menu offers Edit (reuses the same form) and Delete (confirmation dialog). Selecting drives a controlled audience id via `value` / `onChange`.
*

Please select the most appropriate category below or create a new one for your design.

onChange valuenone
Add in a modal
Set `addLayout="modal"` to surface the create form in a centered Dialog instead of the default side Drawer. The form (name + category) is identical either way.

The “Add” button opens the create form in a centered modal.

Auto columns
`cols="auto"` wraps the cards responsively (auto-fill, min ~16rem each) instead of the default fixed 2-column grid. Pass any number to `cols` for a fixed count.

cols="auto" wraps cards responsively instead of a fixed column count.

No add button / search off
Turn off the search field with `searchEnabled={false}` and hide the create button with `showAddButton={false}` for a compact, fixed list of choices.

Search and the add button are turned off for a read-only-ish picker.