The board is already fully functional — it was assembled piece by piece across the last twelve lessons. In this final lesson we step back: the complete file tree, the full KanbanBoard as one reference, and a recap of every Astryx concept you used.
Files this lesson: none to create — page.tsx is still untouched since lesson 1, and the board is complete.
The Completed Application
src/└── app/ ├── globals.css # CSS layer imports + @stylex directive ├── layout.tsx # Root layout (written in lesson 1) ├── page.tsx # <KanbanBoard /> (written once in lesson 1) ├── providers.tsx # Theme + i18n providers + usePreferences() ├── messages.ts # en/fr translation catalogs ├── types.ts # Domain contracts ├── data.ts # Column configs, priorities, initial items ├── styles.ts # Centralized StyleX stylesheet ├── hooks/ │ └── useKanbanDrag.ts # Drag-and-drop interaction hook └── components/ ├── BoardCardBody.tsx # Work item presentation ├── BoardCard.tsx # Interactive card shell ├── BoardColumn.tsx # Column shell & empty state ├── BoardToolbar.tsx # Top bar: sprint, actions, theme & language toggles ├── FloatingCard.tsx # Fixed drag overlay clone └── KanbanBoard.tsx # The board — root compositionNotice what didn’t happen: page.tsx was written once in lesson 1 and never touched again. Everything else grew in place, one lesson at a time.
The Full Reference: KanbanBoard.tsx
The board is the composition root — it owns state (items, sprint), calls the drag hook, and renders the shell, toolbar, columns, and floating overlay. Here is the complete file exactly as it stands after lesson 12:
'use client';
import { useState, type ReactNode } from 'react';import { Section } from '@astryxdesign/core/Section';import { Layout, LayoutHeader, LayoutContent, HStack, VStack,} from '@astryxdesign/core/Layout';import { styles } from '../styles';import { BoardCard } from './BoardCard';import { BoardColumn } from './BoardColumn';import { BoardToolbar } from './BoardToolbar';import { FloatingCard } from './FloatingCard';import { useKanbanDrag } from '../hooks/useKanbanDrag';import { COLUMNS, INITIAL_ITEMS } from '../data';import type { ColumnId, WorkItem } from '../types';
export default function KanbanBoard() { const [items, setItems] = useState<WorkItem[]>(INITIAL_ITEMS); const [sprint, setSprint] = useState('003');
const { drag, itemsByColumn, getColumnRef, getCardRef, onCardPointerDown } = useKanbanDrag(items, setItems);
const moveItem = (id: string, to: ColumnId) => { setItems(prev => prev.map(item => (item.id === id ? { ...item, column: to } : item)), ); };
const renderColumnCards = (colId: ColumnId): ReactNode => { const colItems = itemsByColumn[colId]; const visible = drag ? colItems.filter(it => it.id !== drag.id) : colItems; const ghostTarget = drag && drag.target && drag.target.column === colId ? drag : null;
if (visible.length === 0 && !ghostTarget) { return null; }
const nodes: ReactNode[] = visible.map(it => ( <BoardCard key={it.id} item={it} cardRef={getCardRef(it.id)} onPointerDown={onCardPointerDown} onMove={moveItem} /> ));
if (ghostTarget && ghostTarget.target) { const index = Math.min(ghostTarget.target.index, nodes.length); nodes.splice( index, 0, <VStack key="drag-ghost" xstyle={styles.ghost(ghostTarget.height)} />, ); }
return <VStack gap={2}>{nodes}</VStack>; };
const draggedItem = drag ? items.find(it => it.id === drag.id) : undefined;
return ( <Section height="100dvh"> <Layout height="fill" header={ <LayoutHeader hasDivider padding={4}> <BoardToolbar totalTasks={items.length} sprint={sprint} onSprintChange={setSprint} dragStatus={ drag ? `Dragging ${drag.id} → ${ drag.target ? `${drag.target.column} #${drag.target.index}` : 'no target' }` : undefined } /> </LayoutHeader> } content={ <LayoutContent padding={0}> <HStack gap={4} xstyle={styles.boardColumns}> {COLUMNS.map(meta => ( <BoardColumn key={meta.id} meta={meta} count={itemsByColumn[meta.id].length} contentRef={getColumnRef(meta.id)} > {renderColumnCards(meta.id)} </BoardColumn> ))} </HStack> </LayoutContent> } /> {drag && draggedItem ? ( <FloatingCard drag={drag} item={draggedItem} /> ) : null} </Section> );}What the Board Does
Run npm run dev and visit http://localhost:3000. The finished board:
- Renders four typed columns from
COLUMNS, each with a status dot, tooltip, aligned counter, and compact empty state. - Renders tasks from state with badges, menus, clamped descriptions, and hover elevation.
- Drags cards between columns and reorders them with a floating clone and a ghost slot.
- Lets you switch the active sprint from the toolbar.
- Toggles light/dark/system theme and en/fr language from the toolbar — the same controls you built in lessons 3 and 5, now living in the UI.
- Uses a single
styles.tsstylesheet with dark-mode-aware conditional styles, and auseKanbanDraghook that consumes Astryx’s forwarded-ref contract.
Astryx Vocabulary Recap
| Area | Astryx vocabulary | Where you used it |
|---|---|---|
| Providers | Theme, InternationalizationProvider, usePreferences context | App root: theme, i18n, app state (lessons 1, 3, 5) |
| Layout | Section, Layout, LayoutHeader, LayoutContent, HStack, VStack | App shell, column shells, card content (lessons 2, 9, 11–12) |
| Typography | Heading, Text — type, color, maxLines, hasTabularNumbers | Titles, descriptions, tabular counters (lessons 3, 6–7, 9) |
| Feedback | Card, Badge, StatusDot, EmptyState, Tooltip | Board shells, badges, status dots, empty columns (lessons 6–9) |
| Actions | Toolbar, Selector, Divider, IconButton, Button, Icon, MoreMenu | Top action bar, contextual menus (lessons 7, 10) |
| Styling | xstyle + StyleX — static rules, pseudo-classes, media queries, dynamic functions | Hover elevation, floating overlay, ghost slot (lessons 4, 8, 12) |
| Tokens | --spacing-*, --shadow-*, --color-*, --radius-* | Every custom rule in styles.ts (lesson 5) |
| i18n | useTranslator, catalogs, locale chains | Header, toolbar, counters (lessons 3, 10) |
Course Summary
Congratulations! You have completed the Mastering Astryx course.
You learned how to:
- Configure
@astryxdesign/corewith CSS layer imports and the<Theme>provider (light/dark from day one). - Compose spatial flex structures using
Section,Layout,HStack, andVStack. - Build localized typography and status indicators (
StatusDot,Badge,Text hasTabularNumbers). - Extend design primitives with custom StyleX elevation and dynamic transform functions (
xstyle). - Model domain data against design-system variant vocabularies.
- Deconstruct a complex application into modular, single-responsibility components and a custom hook.
Most importantly, you built one real application — a Kanban board — in place, lesson by lesson.