Content Development Guide
How to create training lessons, quizzes, and courses using the reusable component library
Implementation: packages/ui/src/components/training/
This guide covers how to develop training content using the reusable component library.
Quick Start
// Minimal lesson structure
import { LessonContainer, Callout, KeyTakeaways } from '@repo/ui'
import { Stack, Text } from '@mantine/core'
export default function Lesson01Introduction() {
return (
<LessonContainer
title="Introduction"
subtitle="Getting started with the platform"
duration="10 min"
>
<Stack>
<Text>Your lesson content here...</Text>
<Callout type="info" title="What you'll learn">
<Text>- First topic</Text>
<Text>- Second topic</Text>
</Callout>
<KeyTakeaways items={[
'First key point',
'Second key point'
]} />
</Stack>
</LessonContainer>
)
}Note: Stack uses gap="md" by default from the theme (componentThemes.tsx). Do not override this in lessons.
Component Library
Layout Components
LessonContainer
Wrapper for all lesson content. Provides consistent padding, title, and subtitle.
import { LessonContainer } from '@repo/ui'
<LessonContainer
title="Lesson Title"
subtitle="Course Name"
duration="15 min"
>
{/* Lesson content */}
</LessonContainer>| Prop | Type | Description |
|---|---|---|
title | string | Lesson title displayed in header |
subtitle | string | Course name or section |
duration | string | Estimated reading time |
children | ReactNode | Lesson content |
LessonContent
Styled typography wrapper with proper line height for readability.
import { LessonContent } from '@repo/ui'
<LessonContent>
<h2>Section Title</h2>
<p>Paragraph content with proper styling...</p>
</LessonContent>Content Highlight Components
Callout
Highlight boxes for important information. Four types available.
import { Callout } from '@repo/ui'
<Callout type="info" title="Note">
Information the learner should know.
</Callout>
<Callout type="tip" title="Pro Tip">
Helpful advice or best practices.
</Callout>
<Callout type="warning" title="Caution">
Something to be careful about.
</Callout>
<Callout type="danger" title="Important">
Critical information or common mistakes.
</Callout>| Type | Color | Use For |
|---|---|---|
info | Blue | General information, context |
tip | Green | Best practices, helpful hints |
warning | Yellow | Things to be careful about |
danger | Red | Critical warnings, common mistakes |
QuickFact
Single-line highlight for interesting facts or statistics.
import { QuickFact } from '@repo/ui'
<QuickFact>
AI agents can reduce manual work by up to 80% in repetitive tasks.
</QuickFact>KeyTakeaways
Summary box for lesson conclusions. Place at the end of each lesson.
import { KeyTakeaways } from '@repo/ui'
<KeyTakeaways items={[
'First key point learners should remember',
'Second key point',
'Third key point'
]} />
// Custom title
<KeyTakeaways title="Summary" items={[...]} />Visual Content Components
SectionHeader
Section divider with optional icon and gradient underline. Has built-in spacing (mt="xl" above, mb="md" below) - do not add additional margins.
import { SectionHeader } from '@repo/ui'
import { IconBrain } from '@tabler/icons-react'
<SectionHeader
icon={IconBrain}
title="AI Fundamentals"
color="blue"
order={2}
/>| Prop | Type | Default | Description |
|---|---|---|---|
icon | Icon | - | Tabler icon component |
title | string | - | Section title |
color | string | 'blue' | Mantine color |
order | 2 | 3 | 4 | 2 | Heading level |
FeatureCard
Card for highlighting features or concepts with an icon.
import { FeatureCard } from '@repo/ui'
import { IconBrain } from '@tabler/icons-react'
<FeatureCard
icon={IconBrain}
title="AI Agents"
description="Specialized AI workers that perform specific tasks"
color="violet"
/>ConceptGrid
Grid layout for displaying multiple related concepts.
import { ConceptGrid } from '@repo/ui'
import { IconRoute, IconBrain, IconPlug } from '@tabler/icons-react'
<ConceptGrid
columns={3}
items={[
{
icon: IconRoute,
title: 'Workflows',
description: 'Step-by-step processes',
color: 'blue'
},
{
icon: IconBrain,
title: 'Agents',
description: 'AI decision makers',
color: 'violet'
},
{
icon: IconPlug,
title: 'Integrations',
description: 'External connections',
color: 'cyan'
}
]}
/>StepCard
Numbered step for procedural content.
import { StepCard } from '@repo/ui'
<StepCard step={1} title="Create a new workflow" isActive>
<Text>Navigate to the Workflows section...</Text>
</StepCard>
<StepCard step={2} title="Configure triggers">
<Text>Set up how the workflow starts...</Text>
</StepCard>ProcessTimeline
Vertical timeline for displaying sequential processes, workflows, or step-by-step procedures.
import { ProcessTimeline } from '@repo/ui'
import { IconBrain, IconPlayerPlay, IconCircleCheck, IconRefresh } from '@tabler/icons-react'
<ProcessTimeline
steps={[
{ title: 'Think', description: 'Analyze the situation and plan next action', icon: IconBrain },
{ title: 'Act', description: 'Execute using available tools', icon: IconPlayerPlay },
{ title: 'Evaluate', description: 'Check if goal is achieved', icon: IconCircleCheck },
{ title: 'Loop', description: 'Repeat until task is complete', icon: IconRefresh },
]}
/>| Prop | Type | Default | Description |
|---|---|---|---|
steps | ProcessStep[] | - | Array of steps with title, description, and icon |
active | number | steps.length - 1 | Index of last active step (0-indexed) |
bulletSize | number | 24 | Size of bullet icons in pixels |
ProcessStep interface:
interface ProcessStep {
title: string // Step title
description: string // Step description
icon: Icon // Tabler icon component
}When to use ProcessTimeline:
- Sequential workflows (Trigger -> Step 1 -> Step 2 -> Output)
- Agent loops (Think -> Act -> Evaluate -> Loop)
- Multi-step processes (Receive -> Research -> Score -> Update)
When NOT to use:
- Non-sequential lists (use
Listinstead) - Category lists (Research, Analysis, Creativity - use bullets)
- Comparison tables (use
ReferenceTable)
Code Components
CodeBlock
Syntax-highlighted code with copy button.
import { CodeBlock } from '@repo/ui'
<CodeBlock language="typescript" title="Example Agent">
{`const agent = new Agent({
name: 'My Agent',
model: 'claude-sonnet-4'
})`}
</CodeBlock>
<CodeBlock language="bash" showLineNumbers>
{`npm install @repo/ui
npm run dev`}
</CodeBlock>| Prop | Type | Default | Description |
|---|---|---|---|
language | string | 'typescript' | Syntax highlighting language |
title | string | - | Optional header label |
showLineNumbers | boolean | false | Show line numbers |
children | string | - | Code content |
Supported languages: typescript, javascript, bash, json, sql, yaml, python, etc.
Progress Components
ProgressRing
Circular progress indicator.
import { ProgressRing } from '@repo/ui'
<ProgressRing value={75} label="Course Progress" />
<ProgressRing value={100} label="Complete!" size={80} thickness={8} />LessonNavigation
Sticky bottom bar with prev/next and progress. Used automatically by the lesson route - you don't need to include this in lesson content.
Quiz Components
LessonQuiz
Modal-based quiz with one question at a time.
// Quiz component file (Lesson01Quiz.tsx)
import { LessonQuiz, type QuizQuestion } from '@repo/ui'
const questions: QuizQuestion[] = [
{
id: 'q1',
question: 'What is the primary purpose of workflows?',
options: [
{ value: 'a', label: 'AI decision making' },
{ value: 'b', label: 'Step-by-step automation' },
{ value: 'c', label: 'Data storage' }
],
correctAnswer: 'b'
},
{
id: 'q2',
question: 'When should you use an agent instead of a workflow?',
options: [
{ value: 'a', label: 'For predictable processes' },
{ value: 'b', label: 'For tasks requiring judgment' },
{ value: 'c', label: 'For scheduled tasks' }
],
correctAnswer: 'b'
}
]
// Export config for course.config.ts
export const lesson01QuizConfig = { questions }
export default function Lesson01Quiz({ onComplete, onContinue }) {
return (
<LessonQuiz
questions={questions}
onComplete={(answers, score) => onComplete?.(answers, score)}
onContinue={onContinue}
/>
)
}Quiz Features:
- Questions and options are shuffled each attempt
- 100% passing score required
- Shows correct answers after submission
- Retry available if failed
AssessmentQuiz
Full-page assessment for course certifications. Supports multiple question types and keyboard navigation.
// FinalAssessment.tsx
import { AssessmentQuiz, type AssessmentQuestion, type AssessmentResult } from '@repo/ui'
export const finalAssessmentConfig = {
questions: [
// Radio question (single select)
{
id: 'q1',
type: 'radio',
question: 'What is the primary purpose of workflows?',
description: 'Think about predictability and consistency.',
options: [
{ value: 'a', label: 'AI decision making', description: 'Requires judgment' },
{ value: 'b', label: 'Step-by-step automation', description: 'Predictable processes' },
{ value: 'c', label: 'Data storage', description: 'Persistence layer' }
],
correctAnswer: 'b'
},
// Checkbox question (multi-select)
{
id: 'q2',
type: 'checkbox',
question: 'Which are valid trigger types? (Select all that apply)',
options: [
{ value: 'manual', label: 'Manual (button click)' },
{ value: 'scheduled', label: 'Scheduled (time-based)' },
{ value: 'event', label: 'Event-driven (webhook)' },
{ value: 'invalid', label: 'Telepathic (mind reading)' }
],
correctAnswers: ['manual', 'scheduled', 'event']
}
] as const satisfies readonly AssessmentQuestion[]
}
interface FinalAssessmentProps {
onComplete?: (answers: Record<string, unknown>, score: number) => void
}
export default function FinalAssessment({ onComplete }: FinalAssessmentProps) {
const handleComplete = (result: AssessmentResult) => {
onComplete?.(result.answers, result.score)
}
return (
<AssessmentQuiz
questions={finalAssessmentConfig.questions}
passingScore={80}
onComplete={handleComplete}
shuffle={true}
/>
)
}| Prop | Type | Default | Description |
|---|---|---|---|
questions | AssessmentQuestion[] | - | Array of radio or checkbox questions |
passingScore | number | 80 | Minimum score to pass (0-100) |
onComplete | (result) => void | - | Called when assessment submitted |
onRetry | () => void | - | Called when user retries after failing |
shuffle | boolean | true | Shuffle questions and options |
Question Types:
| Type | Selection | Answer Field |
|---|---|---|
radio | Single option | correctAnswer: string |
checkbox | Multiple options | correctAnswers: string[] |
AssessmentQuiz vs LessonQuiz:
| Feature | LessonQuiz | AssessmentQuiz |
|---|---|---|
| Display | Modal overlay | Full page |
| Question Types | Radio only | Radio + Checkbox |
| Option Descriptions | No | Yes |
| Keyboard Navigation | No | Yes (A-D, Enter) |
| Default Pass Score | 100% | 80% |
| Use Case | End-of-lesson checks | Course certification |
Training-Specific Components
Located in @/features/training/components - these enforce brand consistency.
SectionHeader
Section header with icon and gradient underline. Default color is blue. Inherits built-in spacing (mt="xl" above, mb="md" below).
import { SectionHeader } from '@repo/ui'
import { IconBrain } from '@tabler/icons-react'
<SectionHeader icon={IconBrain} title="AI Fundamentals" />TrainingConceptGrid
ConceptGrid with training brand color enforced.
import { TrainingConceptGrid } from '@/features/training/components'
import { IconRoute, IconBrain } from '@tabler/icons-react'
<TrainingConceptGrid
columns={2}
items={[
{ icon: IconRoute, title: 'Workflows', description: 'Step sequences' },
{ icon: IconBrain, title: 'Agents', description: 'AI decision makers' }
]}
/>ReferenceTable
Tabular data display with glass styling.
import { ReferenceTable } from '@/features/training/components'
import { IconRoute, IconBrain } from '@tabler/icons-react'
<ReferenceTable
columns={[
{ header: 'Component', key: 'name', width: '25%' },
{ header: 'Purpose', key: 'purpose' },
{ header: 'Use When', key: 'when' }
]}
rows={[
{
id: 'workflow',
name: 'Workflow',
purpose: 'Execute predictable steps',
when: 'Process is repeatable',
icon: IconRoute,
color: 'blue'
},
{
id: 'agent',
name: 'Agent',
purpose: 'Make decisions',
when: 'Task requires judgment',
icon: IconBrain,
color: 'violet'
}
]}
/>Lesson Structure Pattern
Follow this structure for consistent lessons:
import {
LessonContainer,
Callout,
KeyTakeaways,
SectionHeader
} from '@repo/ui'
import { Text, Stack } from '@mantine/core'
import { TrainingConceptGrid } from '@/features/training/components'
import { IconTopic } from '@tabler/icons-react'
export default function Lesson01TopicName() {
return (
<LessonContainer
title="Topic Name"
subtitle="Description of what this covers"
duration="10 min"
>
<Stack>
{/* 1. Introduction */}
<Text size="lg">
Opening paragraph that sets context and hooks the learner.
</Text>
{/* 2. Learning objectives */}
<Callout type="info" title="What you'll learn">
<Stack gap="xs">
<Text>- First learning objective</Text>
<Text>- Second learning objective</Text>
<Text>- Third learning objective</Text>
</Stack>
</Callout>
{/* 3. Main content sections */}
<SectionHeader icon={IconTopic} title="Section One" />
<Text>Content for section one...</Text>
<SectionHeader icon={IconTopic} title="Section Two" />
<Text>Content for section two...</Text>
{/* 4. Key takeaways */}
<KeyTakeaways
items={[
'First key point',
'Second key point',
'Third key point'
]}
/>
{/* 5. Transition to next lesson */}
<Callout type="info" title="Next Up">
In the next lesson, we'll explore...
</Callout>
</Stack>
</LessonContainer>
)
}Spacing Standards:
Stackuses theme defaultgap="md"- do not overrideSectionHeaderhas built-in margins (mt="xl",mb="md") - do not add extra spacingListcomponents don't needmtprops - Stack gap handles spacing- Only use
gap="xs"inside Callouts for tightly grouped items
Quiz File Pattern
Each lesson quiz is a separate file:
// Lesson01TopicQuiz.tsx
import { LessonQuiz, type QuizQuestion } from '@repo/ui'
const questions: QuizQuestion[] = [
{
id: 'q1',
question: 'Question text here?',
options: [
{ value: 'a', label: 'Option A' },
{ value: 'b', label: 'Option B' },
{ value: 'c', label: 'Option C' },
{ value: 'd', label: 'Option D' }
],
correctAnswer: 'b'
}
// Add 3-5 questions per quiz
]
// Export for course.config.ts
export const lesson01TopicQuizConfig = { questions }
interface Props {
onComplete?: (answers: Record<string, unknown>, score: number) => void
onContinue?: () => void
}
export default function Lesson01TopicQuiz({ onComplete, onContinue }: Props) {
return (
<LessonQuiz
questions={questions}
onComplete={(answers, score) => onComplete?.(answers, score)}
onContinue={onContinue}
/>
)
}Course Configuration
Register lessons in course.config.ts:
import type { CourseConfig } from '../../types'
// Lesson imports
import Lesson01Topic from './Lesson01Topic'
import Lesson02Topic from './Lesson02Topic'
// Quiz imports
import Lesson01TopicQuiz, { lesson01TopicQuizConfig } from './Lesson01TopicQuiz'
import Lesson02TopicQuiz, { lesson02TopicQuizConfig } from './Lesson02TopicQuiz'
// Final assessment (if certified)
import FinalAssessment, { finalAssessmentConfig } from './FinalAssessment'
export const myCourse: CourseConfig = {
slug: 'my-course',
title: 'My Course',
description: 'Course description for catalog.',
audience: 'customer', // 'internal' | 'developer' | 'customer' | 'public'
estimatedDuration: '60 min',
lessons: [
{
slug: 'topic-one',
title: 'Topic One',
description: 'What this lesson covers.',
duration: '10 min',
order: 1,
component: Lesson01Topic,
quiz: {
passingScore: 100,
questions: lesson01TopicQuizConfig.questions,
component: Lesson01TopicQuiz
}
},
{
slug: 'topic-two',
title: 'Topic Two',
description: 'What this lesson covers.',
duration: '15 min',
order: 2,
component: Lesson02Topic,
quiz: {
passingScore: 100,
questions: lesson02TopicQuizConfig.questions,
component: Lesson02TopicQuiz
}
}
],
assessments: [
{
slug: 'final-assessment',
title: 'Course Certification',
passingScore: 80,
questions: finalAssessmentConfig.questions,
component: FinalAssessment
}
],
certification: {
certificationSlug: 'my-course-certified',
requiredLessons: 'all',
requiredAssessments: [{ slug: 'final-assessment', minScore: 80 }]
}
}Theme Colors
Use the centralized theme for consistency:
import {
TRAINING_PRIMARY_COLOR,
TRAINING_SEMANTIC_COLORS,
getTrainingColor
} from '@/features/training/components'
// Primary color (blue) - use for most content
TRAINING_PRIMARY_COLOR // 'blue'
// Semantic colors - use sparingly for specific meanings
TRAINING_SEMANTIC_COLORS.success // 'green' - positive outcomes
TRAINING_SEMANTIC_COLORS.warning // 'orange' - caution
TRAINING_SEMANTIC_COLORS.error // 'red' - negative outcomes
TRAINING_SEMANTIC_COLORS.neutral // 'gray' - dimmed content
// Helper function
getTrainingColor('success') // 'green'
getTrainingColor() // 'blue' (default)Best Practices
Spacing Guidelines
- Use theme defaults -
Stackusesgap="md"from theme - do not override - No inline margins on Lists - Stack gap handles spacing between elements
- SectionHeader has built-in spacing -
mt="xl"above,mb="md"below - Use
gap="xs"inside Callouts - For tightly grouped items within callouts
Content Guidelines
- Keep lessons focused - One concept per lesson, 8-15 minutes reading time
- Start with context - Opening paragraph explains why this matters
- Use visuals - ConceptGrid, FeatureCard, and diagrams break up text
- End with takeaways - KeyTakeaways summarizes key points
- Preview next lesson - Callout at end creates continuity
Quiz Guidelines
- 3-5 questions per lesson - Tests understanding, not memorization
- Clear correct answers - Avoid trick questions or ambiguous options
- Test key concepts - Questions should cover KeyTakeaways content
Passing Score Standards:
- Lesson quizzes: 100% - Users must answer all questions correctly to proceed
- Final assessments: 80% - Required for certification
Accessibility
- Alt text for images - Describe what the image shows
- Semantic headings - Use proper heading hierarchy (h2, h3, h4)
- Color not sole indicator - Don't rely only on color to convey meaning
- Readable font sizes - LessonContent provides proper sizing
File Structure
apps/command-center/src/features/training/content/courses/{slug}/
├── course.config.ts # Course metadata and lesson registry
├── Lesson01TopicName.tsx # Lesson content component
├── Lesson01TopicNameQuiz.tsx # Inline quiz for lesson
├── Lesson02TopicName.tsx
├── Lesson02TopicNameQuiz.tsx
├── FinalAssessment.tsx # Final certification assessment (optional)
└── PlaceholderLesson.tsx # Placeholder for courses in developmentRelated Documentation
- Course Catalog - All available courses
- LMS Features - Full LMS architecture
- Training Overview - Feature infrastructure
Last Updated: 2026-01-12