Skip to main content

CODITECT Design Pattern Library - Part 2

Continuation: Organisms, Templates, Pages, Assembly Guide

ORGANISMS (Continued)

O4: Navigation Bar

Purpose: Primary site/app navigation

Test Criteria:

composition:
- logo: brand identity
- nav_items: 3-4 main sections
- actions: search, user menu

behavior:
- sticky: remains visible on scroll
- active_state: current page highlighted
- responsive: hamburger on mobile

accessibility:
- role: navigation
- aria_label: "Main navigation"
- aria_current: "page" on active

Code:

<nav class="navbar" role="navigation" aria-label="Main navigation" data-testid="navbar">
<div class="navbar-container">
<!-- Logo -->
<a href="/" class="navbar-logo">
<span class="logo-text">CODITECT</span>
</a>

<!-- Desktop Navigation -->
<div class="navbar-nav">
<a href="/projects" class="nav-link" aria-current="page">
Projects
</a>
<a href="/team" class="nav-link">
Team
</a>
<a href="/issues" class="nav-link nav-link-badge">
Issues
<span class="badge badge-count">3</span>
</a>
</div>

<!-- Actions -->
<div class="navbar-actions">
<div class="search-input search-compact">
<svg class="search-icon" width="16" height="16">...</svg>
<input type="search" class="input" placeholder="Search..." />
</div>
<button class="btn-icon" aria-label="User menu">
<div class="avatar avatar-sm">👤</div>
</button>
</div>

<!-- Mobile Toggle -->
<button class="navbar-toggle" aria-label="Toggle menu" aria-expanded="false">
<span class="toggle-icon"></span>
</button>
</div>

<!-- Mobile Menu (Hidden by default) -->
<div class="navbar-mobile" hidden>
<a href="/projects" class="nav-link-mobile">Projects</a>
<a href="/team" class="nav-link-mobile">Team</a>
<a href="/issues" class="nav-link-mobile">Issues</a>
</div>
</nav>

CSS:

.navbar {
height: 64px;
background: var(--white);
border-bottom: 1px solid var(--gray-200);
position: sticky;
top: 0;
z-index: 100;
}

.navbar-container {
max-width: 100%;
height: 100%;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 32px;
}

.navbar-logo {
text-decoration: none;
}

.logo-text {
font-size: 18px;
font-weight: 600;
color: var(--blue-500);
}

.navbar-nav {
display: flex;
align-items: center;
gap: 32px;
}

.nav-link {
font-size: 14px;
font-weight: 500;
color: var(--gray-600);
text-decoration: none;
position: relative;
}

.nav-link:hover {
color: var(--gray-900);
}

.nav-link[aria-current="page"] {
color: var(--gray-900);
}

.nav-link[aria-current="page"]::after {
content: '';
position: absolute;
bottom: -20px;
left: 0;
right: 0;
height: 2px;
background: var(--blue-500);
}

.nav-link-badge {
display: flex;
align-items: center;
gap: 6px;
}

.navbar-actions {
display: flex;
align-items: center;
gap: 16px;
}

.search-compact {
width: 240px;
}

/* Mobile Toggle */
.navbar-toggle {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
background: none;
border: none;
cursor: pointer;
}

.toggle-icon,
.toggle-icon::before,
.toggle-icon::after {
width: 20px;
height: 2px;
background: var(--gray-600);
transition: all 0.3s;
}

.toggle-icon::before,
.toggle-icon::after {
content: '';
position: absolute;
}

.toggle-icon::before {
transform: translateY(-6px);
}

.toggle-icon::after {
transform: translateY(6px);
}

/* Mobile Menu */
.navbar-mobile {
display: none;
padding: 16px 24px;
background: var(--white);
border-bottom: 1px solid var(--gray-200);
}

.nav-link-mobile {
display: block;
padding: 12px 0;
font-size: 16px;
color: var(--gray-700);
text-decoration: none;
}

/* Responsive */
@media (max-width: 768px) {
.navbar-nav,
.search-compact {
display: none;
}

.navbar-toggle {
display: flex;
}

.navbar-mobile:not([hidden]) {
display: block;
}
}

O5: Empty State

Purpose: Friendly placeholder when no content exists

Test Criteria:

composition:
- illustration: visual element
- title: clear message
- description: helpful context
- action: primary CTA

behavior:
- encouraging: positive tone
- actionable: clear next step
- contextual: relevant to feature

accessibility:
- role: status
- aria_live: polite

Code:

<div class="empty-state" role="status" aria-live="polite" data-testid="empty-state">
<div class="empty-icon">📁</div>
<h2 class="empty-title">No Projects Yet</h2>
<p class="empty-description">
Get started by creating your first project.
Projects help you organize work across teams.
</p>
<button class="btn btn-primary">
<span>+ Create Project</span>
</button>
<a href="/docs" class="empty-link">
Learn more about projects
</a>
</div>

CSS:

.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px 24px;
text-align: center;
min-height: 400px;
}

.empty-icon {
font-size: 64px;
margin-bottom: 24px;
opacity: 0.5;
animation: float 3s ease-in-out infinite;
}

@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}

.empty-title {
font-size: 20px;
font-weight: 600;
color: var(--gray-900);
margin: 0 0 12px 0;
}

.empty-description {
font-size: 14px;
color: var(--gray-600);
max-width: 400px;
margin: 0 0 24px 0;
line-height: 1.6;
}

.empty-link {
margin-top: 16px;
font-size: 14px;
color: var(--blue-500);
text-decoration: none;
}

.empty-link:hover {
text-decoration: underline;
}

O6: Toast Notification

Purpose: Temporary feedback message

Test Criteria:

composition:
- icon: semantic indicator
- message: clear feedback
- action: close button

behavior:
- auto_dismiss: 3-5 seconds
- dismissible: manual close
- stacking: multiple toasts
- position: top-right

accessibility:
- role: status or alert
- aria_live: polite or assertive
- aria_atomic: true

Code:

<div class="toast toast-success"
role="status"
aria-live="polite"
aria-atomic="true"
data-testid="toast">
<div class="toast-icon"></div>
<div class="toast-message">Project created successfully</div>
<button class="toast-close btn-icon" aria-label="Close notification">
×
</button>
</div>

CSS:

.toast {
position: fixed;
top: 80px;
right: 24px;
min-width: 320px;
max-width: 480px;
background: var(--white);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 16px;
display: flex;
align-items: center;
gap: 12px;
z-index: 1000;
animation: slideIn 0.3s ease-out;
}

@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

.toast-success {
border-left: 4px solid var(--green-500);
}

.toast-error {
border-left: 4px solid var(--red-500);
}

.toast-warning {
border-left: 4px solid var(--yellow-500);
}

.toast-icon {
font-size: 20px;
flex-shrink: 0;
}

.toast-message {
flex: 1;
font-size: 14px;
color: var(--gray-900);
}

.toast-close {
width: 24px;
height: 24px;
flex-shrink: 0;
}

/* Toast Container (for stacking) */
.toast-container {
position: fixed;
top: 80px;
right: 24px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 12px;
pointer-events: none;
}

.toast-container .toast {
position: relative;
top: auto;
right: auto;
pointer-events: auto;
}

JavaScript:

class ToastManager {
constructor() {
this.container = this.createContainer();
this.toasts = [];
}

createContainer() {
let container = document.querySelector('.toast-container');
if (!container) {
container = document.createElement('div');
container.className = 'toast-container';
document.body.appendChild(container);
}
return container;
}

show(message, type = 'success', duration = 3000) {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
toast.setAttribute('aria-live', type === 'error' ? 'assertive' : 'polite');
toast.setAttribute('aria-atomic', 'true');

const icons = {
success: '✓',
error: '✕',
warning: '⚠',
info: 'ℹ'
};

toast.innerHTML = `
<div class="toast-icon">${icons[type]}</div>
<div class="toast-message">${message}</div>
<button class="toast-close btn-icon" aria-label="Close notification">×</button>
`;

const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => this.remove(toast));

this.container.appendChild(toast);
this.toasts.push(toast);

if (duration > 0) {
setTimeout(() => this.remove(toast), duration);
}

return toast;
}

remove(toast) {
toast.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => {
toast.remove();
this.toasts = this.toasts.filter(t => t !== toast);
}, 300);
}
}

// Usage
const toast = new ToastManager();
toast.show('Project created successfully', 'success');
toast.show('Error uploading file', 'error', 5000);

TEMPLATES

T1: Dashboard Grid Layout

Purpose: Overview page with card grid

Test Criteria:

composition:
- organisms: navbar, card_grid, empty_state
- layout: responsive_grid

behavior:
- grid_adapts: 4→3→2→1 columns
- cards_consistent: uniform height
- loading_states: skeleton cards

Code:

<div class="page-layout" data-testid="dashboard-template">
<!-- Navbar Organism -->
<nav class="navbar"><!-- ... --></nav>

<!-- Main Content -->
<main class="page-main">
<!-- Page Header -->
<div class="page-header">
<div>
<h1 class="page-title">Projects</h1>
<p class="page-subtitle">12 active projects</p>
</div>
<button class="btn btn-primary">+ New Project</button>
</div>

<!-- Card Grid -->
<div class="card-grid">
<!-- Card Organisms -->
<article class="card"><!-- ... --></article>
<article class="card"><!-- ... --></article>
<article class="card"><!-- ... --></article>
<!-- More cards -->
</div>
</main>
</div>

CSS:

.page-layout {
min-height: 100vh;
background: var(--gray-50);
}

.page-main {
max-width: 1440px;
margin: 0 auto;
padding: 48px;
}

.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 32px;
}

.page-title {
font-size: 24px;
font-weight: 600;
color: var(--gray-900);
margin: 0 0 4px 0;
}

.page-subtitle {
font-size: 14px;
color: var(--gray-600);
margin: 0;
}

.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
}

/* Responsive */
@media (max-width: 1440px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
}
}

@media (max-width: 1024px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}

@media (max-width: 768px) {
.page-main {
padding: 24px 16px;
}

.card-grid {
grid-template-columns: 1fr;
}

.page-header {
flex-direction: column;
gap: 16px;
}

.page-header .btn {
width: 100%;
}
}

T2: Detail View Layout

Purpose: Single entity focus with sidebar

Test Criteria:

composition:
- organisms: navbar, breadcrumb, content, sidebar
- layout: main_content + sidebar

behavior:
- sidebar_toggleable: on mobile
- breadcrumb_navigation: back to list
- content_scrollable: independent

Code:

<div class="page-layout">
<nav class="navbar"><!-- ... --></nav>

<!-- Breadcrumb -->
<div class="breadcrumb">
<a href="/projects">Projects</a>
<span class="breadcrumb-separator"></span>
<span class="breadcrumb-current">Website Redesign</span>
</div>

<main class="detail-layout">
<!-- Main Content -->
<div class="detail-main">
<header class="detail-header">
<h1 class="page-title">Website Redesign</h1>
<div class="status-indicator">
<div class="status-dot status-success"></div>
<span>Active</span>
</div>
</header>

<div class="detail-content">
<!-- Progress Section -->
<section class="detail-section">
<h2 class="section-title">Progress</h2>
<div class="progress-indicator"><!-- ... --></div>
</section>

<!-- More sections -->
</div>
</div>

<!-- Sidebar -->
<aside class="detail-sidebar">
<div class="sidebar-section">
<h3 class="sidebar-title">Quick Stats</h3>
<!-- Stats content -->
</div>
</aside>
</main>
</div>

CSS:

.breadcrumb {
padding: 16px 48px;
font-size: 14px;
color: var(--gray-600);
display: flex;
align-items: center;
gap: 8px;
}

.breadcrumb a {
color: var(--blue-500);
text-decoration: none;
}

.breadcrumb-separator {
color: var(--gray-400);
}

.breadcrumb-current {
color: var(--gray-700);
}

.detail-layout {
display: flex;
gap: 32px;
padding: 0 48px 48px;
max-width: 1440px;
margin: 0 auto;
}

.detail-main {
flex: 1;
min-width: 0;
}

.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32px;
}

.detail-content {
display: flex;
flex-direction: column;
gap: 32px;
}

.detail-section {
background: var(--white);
border-radius: 8px;
padding: 24px;
}

.section-title {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
margin: 0 0 16px 0;
}

.detail-sidebar {
width: 280px;
flex-shrink: 0;
}

.sidebar-section {
background: var(--white);
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
}

.sidebar-title {
font-size: 12px;
font-weight: 600;
color: var(--gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0 0 16px 0;
}

/* Responsive */
@media (max-width: 1024px) {
.detail-layout {
flex-direction: column;
}

.detail-sidebar {
width: 100%;
}
}

T3: Form Layout

Purpose: Data entry with validation

Test Criteria:

composition:
- organisms: navbar, form_sections
- molecules: form_fields, action_buttons

behavior:
- validation: inline errors
- saving: auto-save or manual
- cancellation: confirm if dirty

Code:

<div class="page-layout">
<nav class="navbar"><!-- ... --></nav>

<main class="form-layout">
<div class="form-container">
<header class="form-header">
<h1 class="page-title">Create Project</h1>
<p class="page-subtitle">Enter project details below</p>
</header>

<form class="form" id="project-form">
<!-- Form Section -->
<div class="form-section">
<h2 class="form-section-title">Basic Information</h2>

<div class="form-field">
<label for="project-name" class="form-label">
Project Name
<span class="form-required">*</span>
</label>
<input
id="project-name"
type="text"
class="input"
required
aria-required="true"
/>
</div>

<div class="form-field">
<label for="project-desc" class="form-label">
Description
</label>
<textarea id="project-desc" class="input" rows="4"></textarea>
<span class="form-hint">Optional: Add project overview</span>
</div>
</div>

<!-- Form Section -->
<div class="form-section">
<h2 class="form-section-title">Team & Settings</h2>
<!-- More fields -->
</div>

<!-- Form Actions -->
<div class="form-actions">
<button type="button" class="btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary">Create Project</button>
</div>
</form>
</div>
</main>
</div>

CSS:

.form-layout {
padding: 48px;
max-width: 800px;
margin: 0 auto;
}

.form-container {
background: var(--white);
border-radius: 12px;
padding: 32px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.form-header {
margin-bottom: 32px;
padding-bottom: 24px;
border-bottom: 1px solid var(--gray-200);
}

.form-section {
margin-bottom: 32px;
}

.form-section-title {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
margin: 0 0 20px 0;
}

.form-hint {
display: block;
margin-top: 6px;
font-size: 12px;
color: var(--gray-500);
}

.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 24px;
border-top: 1px solid var(--gray-200);
}

/* Responsive */
@media (max-width: 768px) {
.form-layout {
padding: 24px 16px;
}

.form-container {
padding: 24px;
}

.form-actions {
flex-direction: column-reverse;
}

.form-actions .btn {
width: 100%;
}
}

ASSEMBLY GUIDE

Pattern Combination Matrix

Common Page Types:

Page TypeTemplateOrganismsMoleculesAtoms
DashboardGrid LayoutNavbar, Card GridStatus Indicator, ProgressButton, Badge, Avatar
Detail ViewDetail LayoutNavbar, Content, SidebarForm Field, Avatar StackInput, Status Dot
List ViewGrid LayoutNavbar, Data TableSearch Input, PaginationButton, Badge
FormForm LayoutNavbar, Form SectionsForm Field, Button GroupInput, Checkbox
Empty StateGrid LayoutNavbar, Empty State-Button, Icon

Example Assembly: Project Dashboard

Step 1: Start with Template

<div class="page-layout">
<nav class="navbar"><!-- Add Navbar Organism --></nav>
<main class="page-main">
<!-- Add content here -->
</main>
</div>

Step 2: Add Page Structure

<main class="page-main">
<div class="page-header">
<div>
<h1 class="page-title">Projects</h1>
<p class="page-subtitle">12 active</p>
</div>
<button class="btn btn-primary">+ New</button> <!-- Atom: Button -->
</div>
<div class="card-grid">
<!-- Add Card Organisms -->
</div>
</main>

Step 3: Populate with Organisms

<div class="card-grid">
<!-- Organism: Card -->
<article class="card">
<div class="card-header">
<div class="card-icon">🏢</div>
<div class="card-title-group">
<h3 class="card-title">Acme Corp</h3>
<!-- Molecule: Status Indicator -->
<div class="status-indicator">
<div class="status-dot status-success"></div> <!-- Atom -->
<span class="status-label">Active</span>
</div>
</div>
</div>
<!-- More card content -->
</article>
</div>

Step 4: Add Molecules

<div class="card-content">
<!-- Molecule: Avatar Stack -->
<div class="avatar-stack">
<img src="/user1.jpg" class="avatar avatar-stacked" /> <!-- Atom -->
<img src="/user2.jpg" class="avatar avatar-stacked" />
</div>

<!-- Molecule: Progress Indicator -->
<div class="progress-indicator">
<div class="progress-bar"> <!-- Atom -->
<div class="progress-fill" style="width: 65%;"></div>
</div>
<span class="progress-label">65%</span>
</div>
</div>

Result: Complete Dashboard

Template: Grid Layout

Organism: Navbar
Organism: Card Grid

Molecule: Status Indicator
Molecule: Avatar Stack
Molecule: Progress Indicator

Atoms: Button, Badge, Avatar, Progress Bar, Status Dot

TESTING FRAMEWORK

Component Testing Template

describe('Component: [Name]', () => {
let component;

beforeEach(() => {
component = document.querySelector('[data-testid="component-name"]');
});

describe('Visual Requirements', () => {
test('has correct dimensions', () => {
const styles = getComputedStyle(component);
expect(styles.height).toBe('40px');
expect(styles.borderRadius).toBe('6px');
});

test('uses design tokens', () => {
const styles = getComputedStyle(component);
expect(styles.color).toBe('rgb(17, 24, 39)'); // --gray-900
});
});

describe('Interaction Requirements', () => {
test('responds to user input', () => {
const event = new Event('click');
component.dispatchEvent(event);
// Assert expected behavior
});

test('shows focus state', () => {
component.focus();
expect(document.activeElement).toBe(component);
});
});

describe('Accessibility Requirements', () => {
test('is keyboard navigable', () => {
component.focus();
const keyEvent = new KeyboardEvent('keydown', { key: 'Enter' });
component.dispatchEvent(keyEvent);
// Assert expected behavior
});

test('has proper ARIA attributes', () => {
expect(component.getAttribute('role')).toBeTruthy();
expect(component.getAttribute('aria-label')).toBeTruthy();
});

test('meets color contrast requirements', async () => {
const result = await axe.run(component);
expect(result.violations).toHaveLength(0);
});
});
});

Visual Regression Testing

// Using Playwright for screenshot comparison
import { test, expect } from '@playwright/test';

test('Button visual regression', async ({ page }) => {
await page.goto('/components/button');

const button = page.locator('[data-testid="btn-primary"]');

// Screenshot comparison
await expect(button).toHaveScreenshot('button-primary.png');

// Hover state
await button.hover();
await expect(button).toHaveScreenshot('button-primary-hover.png');

// Focus state
await button.focus();
await expect(button).toHaveScreenshot('button-primary-focus.png');
});

USAGE GUIDELINES

When to Use Which Pattern

Navigation:

  • < 5 items → Navbar with text links
  • 5-10 items → Navbar with dropdown menus
  • 10+ items → Sidebar navigation

Data Display:

  • < 20 items → Card grid
  • 20-100 items → Data table
  • 100+ items → Data table with pagination

Actions:

  • 1 primary action → Single button
  • 2-3 actions → Button group
  • 4+ actions → Dropdown menu

Feedback:

  • Immediate → Toast notification
  • Persistent → Alert banner
  • Critical → Modal dialog

Loading:

  • < 1s load → Spinner
  • 1-3s load → Skeleton screen
  • 3s+ load → Progress bar

CUSTOMIZATION GUIDE

Creating Variants

Example: Creating a "Compact" Card Variant

  1. Identify base pattern:

    .card {
    padding: 20px;
    border-radius: 8px;
    }
  2. Create modifier class:

    .card-compact {
    padding: 12px;
    border-radius: 6px;
    }
    .card-compact .card-title {
    font-size: 14px;
    }
  3. Document usage:

    <!-- Standard Card -->
    <div class="card">...</div>

    <!-- Compact Variant -->
    <div class="card card-compact">...</div>
  4. Add to test suite:

    test('compact variant has reduced padding', () => {
    const card = document.querySelector('.card-compact');
    expect(getComputedStyle(card).padding).toBe('12px');
    });

Theming Support

Creating a Dark Theme:

/* Light theme (default) */
:root {
--bg-primary: #FFFFFF;
--text-primary: #111827;
}

/* Dark theme */
[data-theme="dark"] {
--bg-primary: #1F2937;
--text-primary: #F9FAFB;
}

/* Components automatically adapt */
.card {
background: var(--bg-primary);
color: var(--text-primary);
}

QUICK REFERENCE

Copy-Paste Checklist

When using a pattern, ensure you:

  • Copy HTML structure
  • Include CSS styles
  • Add data-testid attributes
  • Include ARIA attributes
  • Test keyboard navigation
  • Verify color contrast
  • Check mobile responsive
  • Add to test suite

Common Combinations

User Profile Header:

<div class="profile-header">
<div class="avatar avatar-lg"><!-- User Photo --></div>
<div>
<h2>User Name</h2>
<div class="status-indicator">
<div class="status-dot status-success"></div>
<span>Online</span>
</div>
</div>
</div>

Search with Filters:

<div class="search-controls">
<div class="search-input"><!-- Search Molecule --></div>
<select class="input"><!-- Filter Dropdown --></select>
<button class="btn btn-secondary"><!-- Filter Button --></button>
</div>

Action Header:

<div class="page-header">
<div>
<h1>Page Title</h1>
<p>Description</p>
</div>
<div class="button-group">
<button class="btn btn-secondary"><!-- Secondary Action --></button>
<button class="btn btn-primary"><!-- Primary Action --></button>
</div>
</div>

PATTERN LIFECYCLE

Adding New Patterns

  1. Identify Need:

    • Pattern used 3+ times
    • Solves common problem
    • Reusable across contexts
  2. Design Pattern:

    • Sketch component
    • Define variants
    • Document usage
  3. Implement:

    • Write HTML/CSS
    • Add accessibility
    • Create test cases
  4. Document:

    • Add to pattern library
    • Include examples
    • Note test criteria
  5. Review:

    • Accessibility audit
    • Performance check
    • Team feedback

Deprecating Patterns

  1. Mark as deprecated in docs
  2. Provide migration path to replacement
  3. Set sunset date (90 days minimum)
  4. Update all instances in codebase
  5. Remove after sunset

This completes the CODITECT Design Pattern Library. Use as a Lego system to build consistent, accessible, test-driven UIs at speed.