diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs
new file mode 100644
index 000000000000..19dc0ac61f93
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.doc.mjs
@@ -0,0 +1,14 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
+export const doc = {
+ type: 'block',
+ exampleFor: 'ComplexSelector',
+ name: 'Complex Selector — Async Assignment',
+ displayName: 'Complex Selector — Async Assignment',
+ description:
+ 'A reviewer picker using changeAction, so the trigger shows the new value optimistically with a busy spinner while the save settles into a validation status.',
+ isReady: true,
+ aspectRatio: 4 / 3,
+ componentsUsed: ['ComplexSelector', 'RadioList'],
+};
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx
new file mode 100644
index 000000000000..67a9196148a9
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorAsyncAssign.tsx
@@ -0,0 +1,82 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+'use client';
+
+import {useState} from 'react';
+import {
+ ComplexSelector,
+ type ComplexSelectorStatus,
+} from '@astryxdesign/core/ComplexSelector';
+import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
+
+const REVIEWERS = [
+ {
+ value: 'sarah-chen',
+ label: 'Sarah Chen',
+ description: 'Design systems · 2 open reviews',
+ },
+ {
+ value: 'marcus-webb',
+ label: 'Marcus Webb',
+ description: 'Accessibility · 5 open reviews',
+ },
+ {
+ value: 'priya-raman',
+ label: 'Priya Raman',
+ description: 'Platform · 1 open review',
+ },
+];
+
+export default function ComplexSelectorAsyncAssign() {
+ const [reviewer, setReviewer] = useState('');
+ const [isSaved, setIsSaved] = useState(false);
+ const selected = REVIEWERS.find(option => option.value === reviewer);
+
+ let status: ComplexSelectorStatus | undefined;
+ if (selected == null) {
+ status = {type: 'error', message: 'Every draft needs a reviewer'};
+ } else if (isSaved) {
+ status = {type: 'success', message: `Assigned to ${selected.label}`};
+ }
+
+ return (
+ // A radio group commits on arrow navigation, so closing the popup on
+ // change would dismiss it mid-traversal; the user dismisses it instead.
+ {
+ setReviewer(value);
+ setIsSaved(false);
+ }}
+ changeAction={async () => {
+ await new Promise(resolve => {
+ setTimeout(resolve, 900);
+ });
+ setIsSaved(true);
+ }}
+ placeholder="Unassigned"
+ triggerLabel={selected?.label}
+ status={status}>
+ {(value, onChange) => (
+
+ {REVIEWERS.map(option => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs
new file mode 100644
index 000000000000..4cd738dbc824
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.doc.mjs
@@ -0,0 +1,14 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
+export const doc = {
+ type: 'block',
+ exampleFor: 'ComplexSelector',
+ name: 'Complex Selector — Folder Tree',
+ displayName: 'Complex Selector — Folder Tree',
+ description:
+ 'A destination picker whose popup content is a TreeList, so the hierarchy keeps its own tree keyboard navigation.',
+ isReady: true,
+ aspectRatio: 4 / 3,
+ componentsUsed: ['ComplexSelector', 'TreeList'],
+};
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx
new file mode 100644
index 000000000000..d175a591e9ea
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorFolderTree.tsx
@@ -0,0 +1,71 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+'use client';
+
+import {useState} from 'react';
+import {ComplexSelector} from '@astryxdesign/core/ComplexSelector';
+import {TreeList} from '@astryxdesign/core/TreeList';
+
+interface Destination {
+ id: string;
+ path: string;
+}
+
+const FOLDERS = [
+ {
+ id: 'design-systems',
+ label: 'Design systems',
+ children: [
+ {id: 'components', label: 'Components'},
+ {id: 'accessibility', label: 'Accessibility'},
+ {id: 'tokens', label: 'Tokens'},
+ ],
+ },
+ {
+ id: 'research',
+ label: 'Research',
+ children: [
+ {id: 'interviews', label: 'Interviews'},
+ {id: 'field-notes', label: 'Field notes'},
+ ],
+ },
+];
+
+export default function ComplexSelectorFolderTree() {
+ const [destination, setDestination] = useState({
+ id: 'components',
+ path: 'Design systems / Components',
+ });
+
+ return (
+
+ label="Move to folder"
+ description="Where this document should live"
+ width={320}
+ value={destination}
+ onChange={setDestination}
+ triggerLabel={destination.path}>
+ {(value, onChange, close) => (
+ ({
+ id: folder.id,
+ label: folder.label,
+ isExpanded: true,
+ children: folder.children.map(child => ({
+ id: child.id,
+ label: child.label,
+ isSelected: child.id === value.id,
+ onClick: () => {
+ onChange({
+ id: child.id,
+ path: `${folder.label} / ${child.label}`,
+ });
+ close();
+ },
+ })),
+ }))}
+ />
+ )}
+
+ );
+}
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs
new file mode 100644
index 000000000000..c17433436736
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs
@@ -0,0 +1,24 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
+export const doc = {
+ type: 'block',
+ exampleFor: 'ComplexSelector',
+ name: 'Complex Selector',
+ displayName: 'Complex Selector',
+ description:
+ 'A reporting-period picker: quick presets beside a range Calendar, edited as a draft and committed on Apply, in a single field.',
+ isReady: true,
+ aspectRatio: 4 / 3,
+ isShowcase: true,
+ componentsUsed: [
+ 'ComplexSelector',
+ 'Calendar',
+ 'List',
+ 'Button',
+ 'Divider',
+ 'Text',
+ 'HStack',
+ 'VStack',
+ ],
+};
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx
new file mode 100644
index 000000000000..54aea3d3d000
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx
@@ -0,0 +1,119 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+'use client';
+
+import {useState} from 'react';
+import {Button} from '@astryxdesign/core/Button';
+import {
+ Calendar,
+ type DateRange,
+ type ISODateString,
+} from '@astryxdesign/core/Calendar';
+import {ComplexSelector} from '@astryxdesign/core/ComplexSelector';
+import {Divider} from '@astryxdesign/core/Divider';
+import {HStack, VStack} from '@astryxdesign/core/Layout';
+import {List, ListItem} from '@astryxdesign/core/List';
+import {Text} from '@astryxdesign/core/Text';
+
+const PRESETS: Array<{
+ label: string;
+ start: ISODateString;
+ end: ISODateString;
+}> = [
+ {label: 'Last 7 days', start: '2026-03-02', end: '2026-03-08'},
+ {label: 'Last 30 days', start: '2026-02-07', end: '2026-03-08'},
+ {label: 'This quarter', start: '2026-01-01', end: '2026-03-08'},
+ {label: 'Previous quarter', start: '2025-10-01', end: '2025-12-31'},
+];
+
+function formatDay(date: ISODateString, hasYear = false) {
+ return new Date(`${date}T00:00:00`).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: hasYear ? 'numeric' : undefined,
+ });
+}
+
+function formatRange(range: DateRange) {
+ if (range.start == null || range.end == null) {
+ return undefined;
+ }
+ const isSameYear = range.start.slice(0, 4) === range.end.slice(0, 4);
+ return `${formatDay(range.start, !isSameYear)} – ${formatDay(range.end, true)}`;
+}
+
+function DateRangeContent({
+ value,
+ onChange,
+ close,
+}: {
+ value: DateRange;
+ onChange: (value: DateRange) => void;
+ close: () => void;
+}) {
+ const [draft, setDraft] = useState(value);
+ const isComplete = draft.start != null && draft.end != null;
+
+ return (
+
+
+
+ {PRESETS.map(preset => (
+ setDraft({start: preset.start, end: preset.end})}
+ />
+ ))}
+
+
+
+
+
+
+ {formatRange(draft) ?? 'Pick a start and end date'}
+
+
+
+
+ );
+}
+
+export default function ComplexSelectorShowcase() {
+ const [range, setRange] = useState({
+ start: '2026-03-02',
+ end: '2026-03-08',
+ });
+
+ return (
+
+ label="Reporting period"
+ description="Pick a preset or draw a custom range"
+ width={320}
+ value={range}
+ onChange={setRange}
+ placeholder="All time"
+ triggerLabel={formatRange(range)}>
+ {(value, onChange, close) => (
+
+ )}
+
+ );
+}
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs
new file mode 100644
index 000000000000..50f49f9fff73
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.doc.mjs
@@ -0,0 +1,14 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
+export const doc = {
+ type: 'block',
+ exampleFor: 'ComplexSelector',
+ name: 'Complex Selector — Multi-select Filter',
+ displayName: 'Complex Selector — Multi-select Filter',
+ description:
+ 'A stage filter that keeps the popup open while several checkboxes are toggled and closes only when the apply button calls close().',
+ isReady: true,
+ aspectRatio: 4 / 3,
+ componentsUsed: ['ComplexSelector', 'CheckboxList', 'Button', 'VStack'],
+};
diff --git a/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx
new file mode 100644
index 000000000000..9cbe55ae70d0
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/ComplexSelector/ComplexSelectorStageFilter.tsx
@@ -0,0 +1,54 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+'use client';
+
+import {useState} from 'react';
+import {Button} from '@astryxdesign/core/Button';
+import {CheckboxList, CheckboxListItem} from '@astryxdesign/core/CheckboxList';
+import {ComplexSelector} from '@astryxdesign/core/ComplexSelector';
+import {VStack} from '@astryxdesign/core/Layout';
+
+const STAGES = [
+ {value: 'discovery', label: 'Discovery'},
+ {value: 'in-review', label: 'In review'},
+ {value: 'approved', label: 'Approved'},
+ {value: 'shipped', label: 'Shipped'},
+];
+
+export default function ComplexSelectorStageFilter() {
+ const [stages, setStages] = useState(['in-review', 'approved']);
+
+ return (
+
+ label="Pipeline stages"
+ description="Filter the roadmap by delivery stage"
+ width={320}
+ value={stages}
+ onChange={setStages}
+ placeholder="All stages"
+ triggerLabel={
+ stages.length > 0
+ ? `${stages.length} of ${STAGES.length} stages`
+ : undefined
+ }>
+ {(value, onChange, close) => (
+
+
+ {STAGES.map(stage => (
+
+ ))}
+
+
+
+ )}
+
+ );
+}
diff --git a/packages/core/src/ComplexSelector/ComplexSelector.tsx b/packages/core/src/ComplexSelector/ComplexSelector.tsx
index 35da221f6326..15de3e53a3a0 100644
--- a/packages/core/src/ComplexSelector/ComplexSelector.tsx
+++ b/packages/core/src/ComplexSelector/ComplexSelector.tsx
@@ -13,6 +13,7 @@
* - /packages/core/src/ComplexSelector/ComplexSelector.test.tsx
* - /packages/core/src/ComplexSelector/index.ts
* - /apps/storybook/stories/ComplexSelector.stories.tsx
+ * - /packages/cli/assets/templates/blocks/components/ComplexSelector/ (showcase blocks)
*/
import React, {