CODITECT Pattern Library - Missing Organisms (Complete Specifications)
Production-ready complex UI components
O4: Modal (Dialog)
Purpose: Overlay for focused tasks and confirmations
Test Criteria:
composition:
- backdrop: semi-transparent overlay
- dialog: centered container
- header: title + close button
- content: scrollable body
- footer: action buttons
behavior:
- focus_trap: keyboard stays within modal
- esc_close: ESC key closes dialog
- backdrop_click: close on outside click
- scroll_lock: prevent body scroll
accessibility:
- role: dialog
- aria_modal: true
- aria_labelledby: title id
- initial_focus: close or primary action
- return_focus: restore to trigger
Variants:
<!-- Confirmation Modal (Small) -->
<div class="modal-backdrop" data-testid="modal-backdrop">
<div class="modal modal--sm" role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div class="modal__header">
<h2 class="modal__title" id="modal-title">Delete Project</h2>
<button class="modal__close" aria-label="Close dialog">
<svg width="20" height="20" viewBox="0 0 20 20">
<path d="M5 5l10 10M15 5L5 15" stroke="currentColor" stroke-width="2"/>
</svg>
</button>
</div>
<div class="modal__content">
<p>Are you sure you want to delete this project? This action cannot be undone.</p>
</div>
<div class="modal__footer">
<button class="button button--secondary">Cancel</button>
<button class="button button--danger">Delete</button>
</div>
</div>
</div>
<!-- Form Modal (Medium) -->
<div class="modal-backdrop">
<div class="modal modal--md" role="dialog" aria-modal="true" aria-labelledby="modal-title-form">
<div class="modal__header">
<h2 class="modal__title" id="modal-title-form">Create New Task</h2>
<button class="modal__close" aria-label="Close dialog">×</button>
</div>
<div class="modal__content">
<form class="form">
<div class="form-field">
<label for="task-title" class="label">Task Title</label>
<input type="text" id="task-title" class="input" required />
</div>
<div class="form-field">
<label for="task-description" class="label">Description</label>
<textarea id="task-description" class="input" rows="4"></textarea>
</div>
<div class="form-field">
<label for="task-assignee" class="label">Assignee</label>
<select id="task-assignee" class="input">
<option>Select team member...</option>
<option>Alice Johnson</option>
<option>Bob Smith</option>
</select>
</div>
</form>
</div>
<div class="modal__footer">
<button class="button button--secondary">Cancel</button>
<button class="button button--primary">Create Task</button>
</div>
</div>
</div>
<!-- Full-Screen Modal (Large) -->
<div class="modal-backdrop">
<div class="modal modal--lg" role="dialog" aria-modal="true">
<div class="modal__header">
<h2 class="modal__title">Project Details</h2>
<button class="modal__close" aria-label="Close dialog">×</button>
</div>
<div class="modal__content modal__content--scrollable">
<!-- Long scrollable content -->
<section>
<h3>Overview</h3>
<p>Project content...</p>
</section>
<!-- More sections... -->
</div>
<div class="modal__footer modal__footer--sticky">
<button class="button button--secondary">Close</button>
<button class="button button--primary">Save Changes</button>
</div>
</div>
</div>
CSS:
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
z-index: 1000;
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal {
background: var(--white);
border-radius: 12px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 10px 10px -5px rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
max-height: calc(100vh - 32px);
animation: slideUp 0.2s ease-out;
}
@keyframes slideUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Size variants */
.modal--sm {
width: 100%;
max-width: 400px;
}
.modal--md {
width: 100%;
max-width: 600px;
}
.modal--lg {
width: 100%;
max-width: 800px;
}
.modal--full {
width: calc(100vw - 32px);
max-width: none;
}
.modal__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px;
border-bottom: 1px solid var(--gray-200);
flex-shrink: 0;
}
.modal__title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--gray-900);
}
.modal__close {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: none;
background: none;
color: var(--gray-500);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
margin-left: 16px;
}
.modal__close:hover {
background: var(--gray-100);
color: var(--gray-900);
}
.modal__content {
padding: 24px;
overflow-y: auto;
flex: 1;
}
.modal__content--scrollable {
max-height: calc(100vh - 300px);
}
.modal__footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
padding: 16px 24px;
border-top: 1px solid var(--gray-200);
flex-shrink: 0;
}
.modal__footer--sticky {
position: sticky;
bottom: 0;
background: var(--white);
}
/* Prevent body scroll when modal is open */
body.modal-open {
overflow: hidden;
}
O5: DataTable
Purpose: Display and interact with tabular data
Test Criteria:
composition:
- thead: column headers
- tbody: data rows
- atoms: checkboxes (selection)
- molecules: pagination
behavior:
- sortable: click header to sort
- selectable: row checkboxes
- responsive: horizontal scroll on mobile
- sticky_header: fixed during scroll
accessibility:
- role: table (semantic table)
- scope: col/row for headers
- aria_sort: ascending/descending/none
- caption: table description
Variants:
<!-- Basic Data Table -->
<div class="data-table-container" data-testid="data-table">
<table class="data-table">
<caption class="data-table__caption">Team Members (24 total)</caption>
<thead class="data-table__header">
<tr>
<th scope="col" class="data-table__cell data-table__cell--checkbox">
<input type="checkbox" class="checkbox__input" aria-label="Select all rows" />
</th>
<th scope="col" class="data-table__cell data-table__cell--sortable" aria-sort="none">
<button class="data-table__sort-button">
Name
<svg class="data-table__sort-icon" width="12" height="12">
<path d="M6 3l-3 3h6l-3-3zm0 6l-3-3h6l-3 3z" fill="currentColor"/>
</svg>
</button>
</th>
<th scope="col" class="data-table__cell">Email</th>
<th scope="col" class="data-table__cell">Role</th>
<th scope="col" class="data-table__cell data-table__cell--sortable" aria-sort="descending">
<button class="data-table__sort-button">
Status
<svg class="data-table__sort-icon data-table__sort-icon--desc" width="12" height="12">
<path d="M6 9l-3-3h6l-3 3z" fill="currentColor"/>
</svg>
</button>
</th>
<th scope="col" class="data-table__cell data-table__cell--actions">Actions</th>
</tr>
</thead>
<tbody class="data-table__body">
<tr class="data-table__row">
<td class="data-table__cell data-table__cell--checkbox">
<input type="checkbox" class="checkbox__input" aria-label="Select Alice Johnson" />
</td>
<td class="data-table__cell">
<div class="data-table__user">
<img src="/alice.jpg" alt="" class="avatar avatar--sm" />
<span>Alice Johnson</span>
</div>
</td>
<td class="data-table__cell">alice@example.com</td>
<td class="data-table__cell">
<span class="badge badge--primary">Admin</span>
</td>
<td class="data-table__cell">
<div class="status-indicator">
<span class="dot dot--success"></span>
<span>Active</span>
</div>
</td>
<td class="data-table__cell data-table__cell--actions">
<button class="button button--ghost button--sm">Edit</button>
</td>
</tr>
<tr class="data-table__row data-table__row--selected">
<td class="data-table__cell data-table__cell--checkbox">
<input type="checkbox" class="checkbox__input" checked aria-label="Select Bob Smith" />
</td>
<td class="data-table__cell">
<div class="data-table__user">
<img src="/bob.jpg" alt="" class="avatar avatar--sm" />
<span>Bob Smith</span>
</div>
</td>
<td class="data-table__cell">bob@example.com</td>
<td class="data-table__cell">
<span class="badge">Member</span>
</td>
<td class="data-table__cell">
<div class="status-indicator">
<span class="dot dot--success"></span>
<span>Active</span>
</div>
</td>
<td class="data-table__cell data-table__cell--actions">
<button class="button button--ghost button--sm">Edit</button>
</td>
</tr>
<tr class="data-table__row data-table__row--disabled">
<td class="data-table__cell data-table__cell--checkbox">
<input type="checkbox" class="checkbox__input" disabled aria-label="Select Charlie Brown" />
</td>
<td class="data-table__cell">
<div class="data-table__user">
<div class="avatar avatar--sm">CB</div>
<span>Charlie Brown</span>
</div>
</td>
<td class="data-table__cell">charlie@example.com</td>
<td class="data-table__cell">
<span class="badge">Guest</span>
</td>
<td class="data-table__cell">
<div class="status-indicator">
<span class="dot dot--inactive"></span>
<span>Inactive</span>
</div>
</td>
<td class="data-table__cell data-table__cell--actions">
<button class="button button--ghost button--sm" disabled>Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- With Pagination -->
<div class="data-table-wrapper">
<div class="data-table-container">
<!-- Table here -->
</div>
<nav class="pagination" aria-label="Table pagination">
<!-- Pagination controls -->
</nav>
</div>
CSS:
.data-table-container {
overflow-x: auto;
border: 1px solid var(--gray-200);
border-radius: 8px;
background: var(--white);
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.data-table__caption {
padding: 16px;
text-align: left;
font-size: 14px;
font-weight: 500;
color: var(--gray-700);
border-bottom: 1px solid var(--gray-200);
}
.data-table__header {
background: var(--gray-50);
position: sticky;
top: 0;
z-index: 10;
}
.data-table__cell {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--gray-200);
}
.data-table__header .data-table__cell {
font-weight: 600;
color: var(--gray-700);
white-space: nowrap;
}
.data-table__cell--checkbox {
width: 40px;
padding: 12px;
}
.data-table__cell--actions {
width: 100px;
text-align: right;
}
.data-table__sort-button {
display: flex;
align-items: center;
gap: 6px;
border: none;
background: none;
padding: 0;
color: inherit;
font: inherit;
cursor: pointer;
width: 100%;
}
.data-table__sort-button:hover {
color: var(--gray-900);
}
.data-table__sort-icon {
color: var(--gray-400);
transition: transform 0.2s;
}
.data-table__sort-icon--desc {
transform: rotate(180deg);
}
.data-table__row {
transition: background-color 0.2s;
}
.data-table__row:hover {
background: var(--gray-50);
}
.data-table__row--selected {
background: var(--blue-50);
}
.data-table__row--disabled {
opacity: 0.5;
cursor: not-allowed;
}
.data-table__user {
display: flex;
align-items: center;
gap: 12px;
}
/* Responsive */
@media (max-width: 768px) {
.data-table__cell {
padding: 8px 12px;
font-size: 13px;
}
.data-table__cell--actions {
position: sticky;
right: 0;
background: inherit;
}
}
O6: Form
Purpose: Multi-field data collection with validation
Test Criteria:
composition:
- molecules: multiple_form_fields
- atoms: buttons (submit, cancel)
- sections: grouped fields
behavior:
- validation: inline errors
- required_fields: visual indicators
- submit_disabled: until valid
- keyboard: enter submits
accessibility:
- form_element: semantic form tag
- fieldset: group related fields
- legend: section titles
- aria_invalid: on error fields
- error_messages: linked to inputs
Variants:
<!-- User Profile Form -->
<form class="form" data-testid="user-form">
<div class="form__header">
<h2 class="form__title">User Profile</h2>
<p class="form__description">Update your personal information</p>
</div>
<fieldset class="form__section">
<legend class="form__legend">Personal Information</legend>
<div class="form__row">
<div class="form-field">
<label for="first-name" class="label">
First Name
<span class="label__required" aria-label="required">*</span>
</label>
<input
type="text"
id="first-name"
class="input"
required
aria-required="true"
/>
</div>
<div class="form-field">
<label for="last-name" class="label">
Last Name
<span class="label__required" aria-label="required">*</span>
</label>
<input
type="text"
id="last-name"
class="input"
required
aria-required="true"
/>
</div>
</div>
<div class="form-field form-field--error">
<label for="email" class="label label--error">
Email Address
<span class="label__required" aria-label="required">*</span>
</label>
<input
type="email"
id="email"
class="input input--error"
value="invalid-email"
aria-invalid="true"
aria-describedby="email-error"
/>
<span class="form-field__error" id="email-error" role="alert">
Please enter a valid email address
</span>
</div>
</fieldset>
<fieldset class="form__section">
<legend class="form__legend">Preferences</legend>
<div class="form-field">
<label for="timezone" class="label">Timezone</label>
<select id="timezone" class="input">
<option>Select timezone...</option>
<option>America/New_York</option>
<option>Europe/London</option>
<option>Asia/Tokyo</option>
</select>
</div>
<div class="form-field">
<label class="toggle">
<input type="checkbox" class="toggle__input" role="switch" />
<span class="toggle__track">
<span class="toggle__thumb"></span>
</span>
<span class="toggle__label">Email notifications</span>
</label>
</div>
</fieldset>
<div class="form__footer">
<button type="button" class="button button--secondary">Cancel</button>
<button type="submit" class="button button--primary">Save Changes</button>
</div>
</form>
CSS:
.form {
background: var(--white);
border-radius: 8px;
padding: 24px;
}
.form__header {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--gray-200);
}
.form__title {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
color: var(--gray-900);
}
.form__description {
margin: 0;
font-size: 14px;
color: var(--gray-600);
}
.form__section {
border: none;
padding: 0;
margin: 0 0 32px 0;
}
.form__legend {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
margin-bottom: 16px;
padding: 0;
}
.form__row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.form-field {
margin-bottom: 20px;
}
.form-field:last-child {
margin-bottom: 0;
}
.form-field--error .label {
color: var(--red-600);
}
.form-field--error .input {
border-color: var(--red-500);
}
.form-field--error .input:focus {
border-color: var(--red-500);
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
.form-field__error {
display: block;
margin-top: 6px;
font-size: 13px;
color: var(--red-600);
}
.form__footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid var(--gray-200);
}
O7: Sidebar
Purpose: Side navigation panel
Test Criteria:
composition:
- header: logo/title
- nav: navigation links
- footer: user menu/settings
behavior:
- collapsible: toggle expand/collapse
- active_state: current page highlight
- responsive: overlay on mobile
accessibility:
- role: navigation
- aria_label: "Main navigation"
- aria_current: page on active link
- keyboard_nav: arrow keys
Variants:
<!-- Expanded Sidebar -->
<aside class="sidebar" role="navigation" aria-label="Main navigation" data-testid="sidebar">
<div class="sidebar__header">
<img src="/logo.svg" alt="CODITECT" class="sidebar__logo" />
<button class="sidebar__toggle" aria-label="Collapse sidebar">
<svg width="20" height="20">
<path d="M15 5l-5 5 5 5" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</button>
</div>
<nav class="sidebar__nav">
<a href="/dashboard" class="sidebar__link sidebar__link--active" aria-current="page">
<svg class="sidebar__icon" width="20" height="20">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" stroke="currentColor" fill="none"/>
</svg>
<span class="sidebar__text">Dashboard</span>
</a>
<a href="/projects" class="sidebar__link">
<svg class="sidebar__icon" width="20" height="20">
<path d="M9 2h6l4 4v14a2 2 0 01-2 2H7a2 2 0 01-2-2V4a2 2 0 012-2z" stroke="currentColor" fill="none"/>
</svg>
<span class="sidebar__text">Projects</span>
<span class="sidebar__badge">3</span>
</a>
<a href="/team" class="sidebar__link">
<svg class="sidebar__icon" width="20" height="20">
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2M9 11a4 4 0 100-8 4 4 0 000 8z" stroke="currentColor" fill="none"/>
</svg>
<span class="sidebar__text">Team</span>
</a>
<div class="sidebar__divider"></div>
<a href="/settings" class="sidebar__link">
<svg class="sidebar__icon" width="20" height="20">
<circle cx="12" cy="12" r="3" stroke="currentColor" fill="none"/>
<path d="M12 1v6m0 6v6M23 12h-6m-6 0H5" stroke="currentColor" fill="none"/>
</svg>
<span class="sidebar__text">Settings</span>
</a>
</nav>
<div class="sidebar__footer">
<button class="sidebar__user">
<img src="/user.jpg" alt="" class="avatar avatar--sm" />
<div class="sidebar__user-info">
<div class="sidebar__user-name">Alice Johnson</div>
<div class="sidebar__user-email">alice@example.com</div>
</div>
<svg class="sidebar__chevron" width="16" height="16">
<path d="M4 6l4 4 4-4" stroke="currentColor" fill="none"/>
</svg>
</button>
</div>
</aside>
<!-- Collapsed Sidebar -->
<aside class="sidebar sidebar--collapsed" role="navigation" aria-label="Main navigation">
<div class="sidebar__header">
<img src="/icon.svg" alt="CODITECT" class="sidebar__logo sidebar__logo--sm" />
<button class="sidebar__toggle" aria-label="Expand sidebar">
<svg width="20" height="20">
<path d="M9 5l5 5-5 5" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</button>
</div>
<nav class="sidebar__nav">
<a href="/dashboard" class="sidebar__link sidebar__link--active" aria-current="page" title="Dashboard">
<svg class="sidebar__icon" width="20" height="20">
<!-- icon path -->
</svg>
</a>
<!-- More links... -->
</nav>
</aside>
CSS:
.sidebar {
display: flex;
flex-direction: column;
width: 280px;
height: 100vh;
background: var(--white);
border-right: 1px solid var(--gray-200);
transition: width 0.3s;
}
.sidebar--collapsed {
width: 72px;
}
.sidebar__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
padding: 0 20px;
border-bottom: 1px solid var(--gray-200);
flex-shrink: 0;
}
.sidebar__logo {
height: 32px;
transition: opacity 0.2s;
}
.sidebar--collapsed .sidebar__logo {
opacity: 0;
width: 0;
}
.sidebar__toggle {
width: 32px;
height: 32px;
padding: 0;
border: none;
background: none;
color: var(--gray-600);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.sidebar__toggle:hover {
background: var(--gray-100);
color: var(--gray-900);
}
.sidebar__nav {
flex: 1;
padding: 16px 12px;
overflow-y: auto;
}
.sidebar__link {
display: flex;
align-items: center;
gap: 12px;
height: 44px;
padding: 0 12px;
margin-bottom: 4px;
border-radius: 8px;
color: var(--gray-700);
text-decoration: none;
transition: all 0.2s;
position: relative;
}
.sidebar__link:hover {
background: var(--gray-100);
color: var(--gray-900);
}
.sidebar__link--active {
background: var(--blue-50);
color: var(--blue-700);
font-weight: 500;
}
.sidebar__icon {
flex-shrink: 0;
color: currentColor;
}
.sidebar__text {
flex: 1;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar--collapsed .sidebar__text {
opacity: 0;
width: 0;
}
.sidebar__badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
background: var(--blue-500);
color: var(--white);
font-size: 11px;
font-weight: 600;
border-radius: 10px;
}
.sidebar--collapsed .sidebar__badge {
position: absolute;
top: 8px;
right: 8px;
min-width: 8px;
height: 8px;
padding: 0;
font-size: 0;
}
.sidebar__divider {
height: 1px;
background: var(--gray-200);
margin: 12px 0;
}
.sidebar__footer {
padding: 16px;
border-top: 1px solid var(--gray-200);
flex-shrink: 0;
}
.sidebar__user {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 8px;
border: none;
background: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
}
.sidebar__user:hover {
background: var(--gray-100);
}
.sidebar__user-info {
flex: 1;
text-align: left;
}
.sidebar__user-name {
font-size: 14px;
font-weight: 500;
color: var(--gray-900);
}
.sidebar__user-email {
font-size: 12px;
color: var(--gray-600);
}
.sidebar--collapsed .sidebar__user-info {
display: none;
}
.sidebar__chevron {
flex-shrink: 0;
color: var(--gray-400);
}
.sidebar--collapsed .sidebar__chevron {
display: none;
}
/* Mobile overlay */
@media (max-width: 768px) {
.sidebar {
position: fixed;
left: 0;
top: 0;
z-index: 100;
transform: translateX(-100%);
}
.sidebar--open {
transform: translateX(0);
}
}
This completes the missing organisms with full production specifications!