| Field | Value |
|---|
| ID | FRD-042 |
| Owner | David Holmes |
| Status | Draft |
| Priority | P1 — Recipes |
| Size | M (Recipe) |
| Target Release | v2.0.0 |
| Last Updated | 2026-05-26 |
| Related | ADR-027 (Default Tech Stack) |
Document Summary
Create an MDX recipe in Storybook that teaches developers how to compose a complete CRUD (Create, Read, Update, Delete) resource page using design system components. The recipe combines DataGrid, Dialog, SearchInput, Pagination, FilterBar, EmptyState, and Toast into a cohesive page pattern with copy-pasteable code, realistic data, and guidance for pagination, search, bulk actions, and error states.
Introduction
Overview
A CRUD resource page (e.g., “Users”, “Products”, “Orders”) is the most common page pattern in B2B SaaS applications. Developers building these pages need to wire together 7-10 design system components with query state, form validation, toast notifications, and loading/error/empty states. Today, there is no canonical recipe showing how to do this correctly. Developers assemble these pages ad-hoc, resulting in inconsistent patterns, missing states, and duplicated logic.
Goals
- Provide a single MDX recipe page in Storybook that shows the complete CRUD page composition pattern.
- Include copy-pasteable code blocks for each section: list view with DataGrid, create/edit Dialog with form, delete confirmation, search/filter bar, pagination, bulk actions, and empty/error/loading states.
- Use realistic sample data (e.g., a “Team Members” resource) so developers can see the pattern in context.
- Link to each component’s Storybook docs page and relevant ADRs.
- Document when to use this recipe and when to use an alternative pattern.
Non-Goals
- Building new components (all components must already exist in the design system).
- Implementing backend API logic — the recipe shows frontend composition only.
- Providing a full working example application (that belongs in
examples/).
- Covering every possible CRUD variation (e.g., inline editing, tree-structured resources).
Scope
In Scope
| Item | Description |
|---|
| MDX recipe page | src/docs/recipes/crud-resource-page.mdx with Storybook sidebar entry |
| List view composition | DataGrid + SortableColumnHeader + Pagination + SearchInput + FilterBar |
| Create/Edit flow | Dialog with form fields, validation, submit/cancel, toast feedback |
| Delete flow | AlertDialog confirmation, toast feedback, optimistic update pattern |
| Bulk actions | Checkbox selection in DataGrid, bulk action toolbar, confirmation dialog |
| State management | Loading skeletons, empty state, error state, no-results state |
| Realistic sample data | TypeScript interface + mock data for a “Team Members” resource |
| Overview page update | Add CRUD recipe entry to src/docs/recipes/00-overview.mdx |
Out of Scope
| Item | Reason |
|---|
| Server-side pagination implementation | Backend-specific; recipe covers the client-side wiring |
| TanStack Query integration details | Query library specifics vary by project; recipe shows the component composition |
| Inline editing pattern | Different enough to warrant a separate recipe |
| Mobile-responsive layout | Recipe focuses on desktop layout; responsive behavior is a component concern |
Users and Pain Points
| User | Pain Point |
|---|
| Application developer | No reference for how to wire DataGrid + Dialog + Toast together for a standard CRUD page |
| Application developer | Unclear which empty state component to use (EmptyState vs. NoResultsState) and when |
| Application developer | Missing bulk action patterns — selection state, toolbar visibility, confirmation flow |
| Application developer | Pagination and search not integrated — unclear how filter state and page state interact |
| New team member | No canonical “this is how we build a resource page” guide |
Definitions
| Term | Definition |
|---|
| CRUD | Create, Read, Update, Delete — the four basic operations on a persistent resource |
| Resource page | A full-page view showing a list of resources with actions to create, edit, and delete them |
| Bulk action | An operation performed on multiple selected resources simultaneously |
| Optimistic update | Updating the UI immediately before the server confirms the operation |
Current State
- The
src/docs/recipes/00-overview.mdx page lists “CRUD Resource Page” as a planned recipe with status “Planned”.
- All required components exist:
data-grid.tsx, dialog.tsx, alert-dialog.tsx, search-input.tsx, pagination.tsx, empty-state.tsx, no-results-state.tsx, toast.tsx, filter-bar.tsx, sortable-column-header.tsx, list-toolbar.tsx.
- No recipe MDX file exists yet.
- No code examples exist for the full CRUD page composition pattern.
Proposed Solution
Create src/docs/recipes/crud-resource-page.mdx with the following structure:
- Introduction — What this recipe builds, when to use it, when not to use it.
- Component inventory — Table listing every component used with links to its docs.
- Data model — TypeScript interface for the sample resource and mock data.
- List view — DataGrid configuration, column definitions, sorting, pagination.
- Search and filter — SearchInput + FilterBar wired to the data query.
- Create/Edit dialog — Dialog with form fields, validation, submit handler, toast.
- Delete confirmation — AlertDialog with confirmation text, delete handler, toast.
- Bulk actions — Selection state, bulk action toolbar, confirmation flow.
- Loading, empty, and error states — Skeleton loading, EmptyState for zero resources, NoResultsState for zero search results, error alert.
- Full composition — Complete code block bringing all sections together.
Each section includes a copy-pasteable code block and a brief explanation of the pattern.
Requirements
| ID | Requirement | Priority |
|---|
| REQ-01 | Recipe is a single MDX file in src/docs/recipes/ | Must |
| REQ-02 | Recipe appears in the Storybook sidebar under “Docs/Best Practices/Recipes” | Must |
| REQ-03 | All code blocks are copy-pasteable and self-contained | Must |
| REQ-04 | Recipe uses realistic sample data with a TypeScript interface | Must |
| REQ-05 | Recipe covers all CRUD operations (create, read, update, delete) | Must |
| REQ-06 | Recipe documents pagination, search, and filter integration | Must |
| REQ-07 | Recipe documents bulk action pattern | Must |
| REQ-08 | Recipe shows loading, empty, error, and no-results states | Must |
| REQ-09 | Recipe links to each component’s Storybook docs page | Should |
| REQ-10 | Recipe references relevant ADRs | Should |
Functional Requirements
| ID | Description | Acceptance |
|---|
| FR-01 | MDX file renders in Storybook without errors | pnpm build-storybook succeeds |
| FR-02 | All code blocks compile when extracted to a standalone TSX file | Manual verification |
| FR-03 | Recipe overview page lists CRUD recipe as “Available” | Status column updated in overview.mdx |
| FR-04 | Each section has a “When to use” / “When not to use” callout | Visual verification |
Non-Functional Requirements
| ID | Description | Target |
|---|
| NFR-01 | Recipe page load time in Storybook | Under 2 seconds |
| NFR-02 | Code block line count per section | Under 80 lines per block; full composition under 200 lines |
| NFR-03 | Readability | A developer unfamiliar with the design system can follow the recipe without external documentation |
API/Interface Requirements
| Interface | Requirement |
|---|
| MDX file | Must use “ for sidebar placement |
| Code blocks | Must use fenced code blocks with tsx language identifier |
| Component imports | Must use the public package import path |
Accessibility Requirements
| ID | Requirement |
|---|
| A11Y-01 | Code examples must include proper aria-label props on DataGrid, Dialog, and action buttons |
| A11Y-02 | Delete confirmation must show the resource name in the dialog for screen reader context |
| A11Y-03 | Bulk action toolbar must announce selection count to screen readers |
| A11Y-04 | Empty and error states must use appropriate ARIA roles (status, alert) |
Content and Documentation Requirements
| ID | Requirement |
|---|
| DOC-01 | Update src/docs/recipes/00-overview.mdx to change CRUD recipe status from “Planned” to “Available” |
| DOC-02 | Each recipe section must have a 1-2 sentence explanation before the code block |
| DOC-03 | Include a “Component inventory” table linking to each used component’s docs |
Dependencies
| Dependency | Type | Risk |
|---|
| DataGrid component | Internal | Must support selection, sorting, and column configuration |
| Dialog component | Internal | Must support form content and controlled open state |
| Toast component | Internal | Must support success and error variants |
| Pagination component | Internal | Must support controlled page state |
| FilterBar component | Internal | Must exist and be functional |
Risks and Tradeoffs
| Risk | Impact | Mitigation |
|---|
| Code examples may not compile with future component API changes | Stale recipe | Include recipe code in CI typecheck or add a compile test |
| Recipe may be too long for a single MDX page | Poor readability | Use collapsible sections; keep individual blocks under 80 lines |
| Sample data may not resemble real-world schemas | Reduced usefulness | Use a realistic “Team Members” resource with common fields (name, email, role, status, created date) |
Open Questions
| # | Question | Status |
|---|
| 1 | Should the recipe include TanStack Query hooks or stay framework-agnostic with plain fetch? | Open |
| 2 | Should the full composition block be a live Storybook story or just a static code block? | Open |
| 3 | Should inline editing be a separate recipe or a subsection of this one? | Open — leaning separate |
Acceptance Criteria
LLM Handoff Instructions
When an LLM agent picks up this FRD:
- Read
src/docs/recipes/00-overview.mdx for the recipe structure and conventions.
- Read the component source files for each component used:
data-grid.tsx, dialog.tsx, alert-dialog.tsx, search-input.tsx, pagination.tsx, empty-state.tsx, no-results-state.tsx, toast.tsx, filter-bar.tsx, sortable-column-header.tsx, list-toolbar.tsx.
- Read each component’s
.stories.tsx file to understand the established usage patterns.
- Create
src/docs/recipes/crud-resource-page.mdx with the section structure described in the Proposed Solution.
- Use a “Team Members” resource as sample data with fields:
id, name, email, role (Admin/Member/Viewer), status (Active/Invited/Deactivated), createdAt.
- All code blocks must use the public package import path (not relative imports).
- After creating the file, run
pnpm build-storybook to verify it renders correctly.
- Update
src/docs/recipes/00-overview.mdx to mark the CRUD recipe as available.
Decision Log
| Date | Decision | Rationale |
|---|
| 2026-05-26 | Use “Team Members” as the sample resource | Universally relatable B2B pattern; exercises all CRUD operations plus role-based access |
| 2026-05-26 | Static code blocks rather than live stories | Recipes should teach the pattern, not demo a running implementation; live examples go in examples/ |
Document History
| Date | Version | Author | Changes |
|---|
| 2026-05-26 | 0.1 | David Holmes | Initial draft |