Skip to main content

CODITECT Pattern Library: Complete Atom Specifications

Missing Atoms - Full Implementation Specs

A8: Label

Purpose: Form field labels with required/optional indicators

Test Criteria:

visual:
- font_size: 14px
- font_weight: 500
- color: gray-700
- margin_bottom: 6px

semantic:
- for_attribute: matches input id
- required_indicator: asterisk (*)
- optional_indicator: "(optional)"

accessibility:
- associated_with_input: via for/id
- required_asterisk: aria-label="required"
- visible: not display:none

Variants:

<!-- Basic Label -->
<label for="email" class="label" data-testid="label-basic">
Email Address
</label>

<!-- Required Field -->
<label for="password" class="label" data-testid="label-required">
Password
<span class="label-required" aria-label="required">*</span>
</label>

<!-- Optional Field -->
<label for="company" class="label" data-testid="label-optional">
Company
<span class="label-optional">(optional)</span>
</label>

<!-- With Hint -->
<label for="username" class="label" data-testid="label-with-hint">
Username
<span class="label-hint">Must be unique</span>
</label>

<!-- Disabled State -->
<label for="locked" class="label label-disabled" data-testid="label-disabled">
Locked Field
</label>

<!-- Error State -->
<label for="invalid" class="label label-error" data-testid="label-error">
Invalid Field
</label>

CSS:

.label {
display: block;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-700);
margin-bottom: var(--space-2);
line-height: var(--line-height-normal);
}

.label-required {
color: var(--color-red-500);
margin-left: var(--space-1);
}

.label-optional {
font-weight: var(--font-weight-normal);
color: var(--color-gray-500);
margin-left: var(--space-1);
}

.label-hint {
display: block;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-normal);
color: var(--color-gray-500);
margin-top: var(--space-1);
}

.label-disabled {
color: var(--color-gray-400);
cursor: not-allowed;
}

.label-error {
color: var(--color-red-700);
}

Test Cases:

describe('Label Atom', () => {
test('associates with input via for/id', () => {
render(
<>
<label htmlFor="test-input" className="label">Test</label>
<input id="test-input" />
</>
);
const label = screen.getByText('Test');
const input = screen.getByRole('textbox');
expect(label.htmlFor).toBe(input.id);
});

test('required indicator has aria-label', () => {
render(
<label>
Field<span className="label-required" aria-label="required">*</span>
</label>
);
const required = screen.getByLabelText('required');
expect(required).toHaveTextContent('*');
});

test('disabled state has correct styling', () => {
render(<label className="label label-disabled">Disabled</label>);
const label = screen.getByText('Disabled');
expect(label).toHaveClass('label-disabled');
});
});

A9: Icon

Purpose: SVG icons with consistent sizing and colors

Test Criteria:

visual:
- size: 16px (default), 12/20/24px variants
- color: currentColor (inherits from parent)
- display: inline-flex

behavior:
- interactive: cursor pointer if clickable
- aria_hidden: true if decorative
- aria_label: present if interactive

accessibility:
- role: img if semantic
- focusable: false if decorative
- clickable: has click handler if interactive

Variants:

<!-- Decorative Icon (No semantic meaning) -->
<svg
class="icon"
width="16"
height="16"
aria-hidden="true"
focusable="false"
data-testid="icon-decorative"
>
<path d="M8 2v12M2 8h12" stroke="currentColor" stroke-width="2"/>
</svg>

<!-- Semantic Icon -->
<svg
class="icon"
width="16"
height="16"
role="img"
aria-label="Add new item"
data-testid="icon-semantic"
>
<path d="M8 2v12M2 8h12" stroke="currentColor" stroke-width="2"/>
</svg>

<!-- Interactive Icon -->
<button class="icon-button" aria-label="Close">
<svg
class="icon"
width="16"
height="16"
aria-hidden="true"
data-testid="icon-interactive"
>
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2"/>
</svg>
</button>

<!-- Size Variants -->
<svg class="icon icon--sm" width="12" height="12">...</svg>
<svg class="icon icon--md" width="16" height="16">...</svg>
<svg class="icon icon--lg" width="20" height="20">...</svg>
<svg class="icon icon--xl" width="24" height="24">...</svg>

<!-- Color Variants -->
<svg class="icon icon--primary">...</svg>
<svg class="icon icon--success">...</svg>
<svg class="icon icon--warning">...</svg>
<svg class="icon icon--error">...</svg>

CSS:

.icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: currentColor;
fill: currentColor;
stroke: currentColor;
}

/* Size variants */
.icon--sm {
width: 12px;
height: 12px;
}

.icon--md {
width: 16px;
height: 16px;
}

.icon--lg {
width: 20px;
height: 20px;
}

.icon--xl {
width: 24px;
height: 24px;
}

/* Color variants */
.icon--primary {
color: var(--color-blue-500);
}

.icon--success {
color: var(--color-green-500);
}

.icon--warning {
color: var(--color-yellow-500);
}

.icon--error {
color: var(--color-red-500);
}

.icon--muted {
color: var(--color-gray-400);
}

/* Interactive state */
.icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--space-1);
background: none;
border: none;
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: background-color var(--transition-fast);
}

.icon-button:hover {
background-color: var(--color-gray-100);
}

.icon-button:focus-visible {
outline: 2px solid var(--color-blue-500);
outline-offset: 2px;
}

Icon Library (Common Icons):

// src/components/atoms/Icon/icons.js
export const icons = {
plus: '<path d="M12 5v14m-7-7h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
close: '<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
check: '<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
chevronDown: '<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
chevronRight: '<path d="M9 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
search: '<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" fill="none"/><path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
user: '<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2" fill="none"/><path d="M4 20c0-4 3.5-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="2"/>',
settings: '<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" fill="none"/><path d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42m12.72-12.72l1.42-1.42" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
edit: '<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
delete: '<path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14zM10 11v6m4-6v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
info: '<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/><path d="M12 16v-4m0-4h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
warning: '<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4m0 4h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
error: '<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"/><path d="M15 9l-6 6m0-6l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
};

A10: Checkbox

Purpose: Multiple selection control

Test Criteria:

visual:
- size: 20px × 20px
- border: 2px solid gray-300
- border_radius: 4px
- checkmark: visible when checked

states:
- unchecked: default
- checked: selected
- indeterminate: partial selection
- disabled: not interactive

accessibility:
- role: checkbox
- aria_checked: true/false/mixed
- keyboard: Space toggles
- label_associated: true

Variants:

<!-- Basic Checkbox -->
<label class="checkbox" data-testid="checkbox-basic">
<input type="checkbox" class="checkbox__input" />
<span class="checkbox__box">
<svg class="checkbox__checkmark" viewBox="0 0 16 16">
<path d="M3 8l4 4 6-8" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</span>
<span class="checkbox__label">Accept terms</span>
</label>

<!-- Checked State -->
<label class="checkbox">
<input type="checkbox" class="checkbox__input" checked />
<span class="checkbox__box">
<svg class="checkbox__checkmark" viewBox="0 0 16 16">
<path d="M3 8l4 4 6-8" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</span>
<span class="checkbox__label">Checked</span>
</label>

<!-- Indeterminate State -->
<label class="checkbox">
<input type="checkbox" class="checkbox__input checkbox__input--indeterminate" />
<span class="checkbox__box">
<svg class="checkbox__checkmark checkbox__checkmark--indeterminate" viewBox="0 0 16 16">
<path d="M4 8h8" stroke="currentColor" stroke-width="2"/>
</svg>
</span>
<span class="checkbox__label">Select all</span>
</label>

<!-- Disabled State -->
<label class="checkbox checkbox--disabled">
<input type="checkbox" class="checkbox__input" disabled />
<span class="checkbox__box">
<svg class="checkbox__checkmark" viewBox="0 0 16 16">
<path d="M3 8l4 4 6-8" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</span>
<span class="checkbox__label">Disabled option</span>
</label>

<!-- With Description -->
<label class="checkbox">
<input type="checkbox" class="checkbox__input" />
<span class="checkbox__box">
<svg class="checkbox__checkmark" viewBox="0 0 16 16">
<path d="M3 8l4 4 6-8" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</span>
<span class="checkbox__content">
<span class="checkbox__label">Enable notifications</span>
<span class="checkbox__description">Receive email updates</span>
</span>
</label>

CSS:

.checkbox {
display: inline-flex;
align-items: flex-start;
gap: var(--space-2);
cursor: pointer;
user-select: none;
}

.checkbox--disabled {
opacity: 0.5;
cursor: not-allowed;
}

.checkbox__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}

.checkbox__box {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border: 2px solid var(--color-gray-300);
border-radius: var(--border-radius-sm);
background: var(--color-white);
flex-shrink: 0;
transition: all var(--transition-fast);
}

.checkbox__input:checked + .checkbox__box {
background: var(--color-blue-500);
border-color: var(--color-blue-500);
}

.checkbox__input:focus-visible + .checkbox__box {
outline: 2px solid var(--color-blue-500);
outline-offset: 2px;
}

.checkbox__checkmark {
width: 14px;
height: 14px;
color: var(--color-white);
opacity: 0;
transform: scale(0.8);
transition: all var(--transition-fast);
}

.checkbox__input:checked + .checkbox__box .checkbox__checkmark {
opacity: 1;
transform: scale(1);
}

.checkbox__input--indeterminate + .checkbox__box {
background: var(--color-blue-500);
border-color: var(--color-blue-500);
}

.checkbox__input--indeterminate + .checkbox__box .checkbox__checkmark--indeterminate {
opacity: 1;
}

.checkbox__content {
display: flex;
flex-direction: column;
gap: var(--space-1);
}

.checkbox__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-900);
line-height: var(--line-height-normal);
}

.checkbox__description {
font-size: var(--font-size-xs);
color: var(--color-gray-600);
line-height: var(--line-height-normal);
}

/* Hover state */
.checkbox:hover .checkbox__box {
border-color: var(--color-blue-500);
}

.checkbox:hover .checkbox__input:checked + .checkbox__box {
background: var(--color-blue-600);
border-color: var(--color-blue-600);
}

JavaScript for Indeterminate:

// Set indeterminate state programmatically
const checkbox = document.querySelector('.checkbox__input--indeterminate');
checkbox.indeterminate = true;

// Example: "Select All" with indeterminate
function updateSelectAll(items) {
const selectAll = document.getElementById('select-all');
const checkedCount = items.filter(item => item.checked).length;

if (checkedCount === 0) {
selectAll.checked = false;
selectAll.indeterminate = false;
} else if (checkedCount === items.length) {
selectAll.checked = true;
selectAll.indeterminate = false;
} else {
selectAll.checked = false;
selectAll.indeterminate = true;
}
}

A11: Radio Button

Purpose: Single selection from multiple options

Test Criteria:

visual:
- size: 20px × 20px
- border: 2px solid gray-300
- border_radius: 50% (circle)
- inner_dot: 8px when selected

states:
- unchecked: default
- checked: selected (exclusive)
- disabled: not interactive

accessibility:
- role: radio
- aria_checked: true/false
- keyboard: Arrow keys navigate group
- radiogroup: parent container

Variants:

<!-- Radio Group -->
<div class="radio-group" role="radiogroup" aria-labelledby="size-label">
<span id="size-label" class="radio-group__label">Size</span>

<!-- Radio Option -->
<label class="radio" data-testid="radio-small">
<input type="radio" name="size" value="small" class="radio__input" />
<span class="radio__circle">
<span class="radio__dot"></span>
</span>
<span class="radio__label">Small</span>
</label>

<label class="radio" data-testid="radio-medium">
<input type="radio" name="size" value="medium" class="radio__input" checked />
<span class="radio__circle">
<span class="radio__dot"></span>
</span>
<span class="radio__label">Medium</span>
</label>

<label class="radio" data-testid="radio-large">
<input type="radio" name="size" value="large" class="radio__input" />
<span class="radio__circle">
<span class="radio__dot"></span>
</span>
<span class="radio__label">Large</span>
</label>
</div>

<!-- Disabled Option -->
<label class="radio radio--disabled">
<input type="radio" name="option" class="radio__input" disabled />
<span class="radio__circle">
<span class="radio__dot"></span>
</span>
<span class="radio__label">Unavailable</span>
</label>

<!-- With Description -->
<label class="radio">
<input type="radio" name="plan" class="radio__input" />
<span class="radio__circle">
<span class="radio__dot"></span>
</span>
<span class="radio__content">
<span class="radio__label">Pro Plan</span>
<span class="radio__description">$29/month - All features</span>
</span>
</label>

CSS:

.radio-group {
display: flex;
flex-direction: column;
gap: var(--space-3);
}

.radio-group__label {
display: block;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-700);
margin-bottom: var(--space-2);
}

.radio {
display: inline-flex;
align-items: flex-start;
gap: var(--space-2);
cursor: pointer;
user-select: none;
}

.radio--disabled {
opacity: 0.5;
cursor: not-allowed;
}

.radio__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}

.radio__circle {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border: 2px solid var(--color-gray-300);
border-radius: 50%;
background: var(--color-white);
flex-shrink: 0;
transition: all var(--transition-fast);
}

.radio__input:checked + .radio__circle {
border-color: var(--color-blue-500);
}

.radio__input:focus-visible + .radio__circle {
outline: 2px solid var(--color-blue-500);
outline-offset: 2px;
}

.radio__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--color-blue-500);
opacity: 0;
transform: scale(0);
transition: all var(--transition-fast);
}

.radio__input:checked + .radio__circle .radio__dot {
opacity: 1;
transform: scale(1);
}

.radio__content {
display: flex;
flex-direction: column;
gap: var(--space-1);
}

.radio__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-900);
line-height: var(--line-height-normal);
}

.radio__description {
font-size: var(--font-size-xs);
color: var(--color-gray-600);
line-height: var(--line-height-normal);
}

/* Hover state */
.radio:hover .radio__circle {
border-color: var(--color-blue-500);
}

A12: Toggle (Switch)

Purpose: Binary on/off control

Test Criteria:

visual:
- width: 44px
- height: 24px
- border_radius: 12px (pill shape)
- thumb: 20px circle

states:
- off: gray background, thumb left
- on: blue background, thumb right
- disabled: reduced opacity

accessibility:
- role: switch
- aria_checked: true/false
- keyboard: Space toggles
- label: visible or aria-label

Variants:

<!-- Basic Toggle (Off) -->
<label class="toggle" data-testid="toggle-off">
<input type="checkbox" role="switch" class="toggle__input" />
<span class="toggle__track">
<span class="toggle__thumb"></span>
</span>
<span class="toggle__label">Email notifications</span>
</label>

<!-- Toggle (On) -->
<label class="toggle" data-testid="toggle-on">
<input type="checkbox" role="switch" class="toggle__input" checked />
<span class="toggle__track">
<span class="toggle__thumb"></span>
</span>
<span class="toggle__label">Push notifications</span>
</label>

<!-- Disabled Toggle -->
<label class="toggle toggle--disabled">
<input type="checkbox" role="switch" class="toggle__input" disabled />
<span class="toggle__track">
<span class="toggle__thumb"></span>
</span>
<span class="toggle__label">Disabled feature</span>
</label>

<!-- With Description -->
<label class="toggle">
<input type="checkbox" role="switch" class="toggle__input" />
<span class="toggle__track">
<span class="toggle__thumb"></span>
</span>
<span class="toggle__content">
<span class="toggle__label">Auto-save</span>
<span class="toggle__description">Save changes automatically</span>
</span>
</label>

<!-- Small Size -->
<label class="toggle toggle--sm">
<input type="checkbox" role="switch" class="toggle__input" />
<span class="toggle__track">
<span class="toggle__thumb"></span>
</span>
<span class="toggle__label">Compact</span>
</label>

CSS:

.toggle {
display: inline-flex;
align-items: flex-start;
gap: var(--space-3);
cursor: pointer;
user-select: none;
}

.toggle--disabled {
opacity: 0.5;
cursor: not-allowed;
}

.toggle__input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}

.toggle__track {
position: relative;
display: block;
width: 44px;
height: 24px;
background: var(--color-gray-300);
border-radius: 12px;
flex-shrink: 0;
transition: background-color var(--transition-base);
}

.toggle__input:checked + .toggle__track {
background: var(--color-blue-500);
}

.toggle__input:focus-visible + .toggle__track {
outline: 2px solid var(--color-blue-500);
outline-offset: 2px;
}

.toggle__thumb {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: var(--color-white);
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
transition: transform var(--transition-base);
}

.toggle__input:checked + .toggle__track .toggle__thumb {
transform: translateX(20px);
}

.toggle__content {
display: flex;
flex-direction: column;
gap: var(--space-1);
}

.toggle__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-gray-900);
line-height: var(--line-height-normal);
}

.toggle__description {
font-size: var(--font-size-xs);
color: var(--color-gray-600);
line-height: var(--line-height-normal);
}

/* Small variant */
.toggle--sm .toggle__track {
width: 36px;
height: 20px;
border-radius: 10px;
}

.toggle--sm .toggle__thumb {
width: 16px;
height: 16px;
}

.toggle--sm .toggle__input:checked + .toggle__track .toggle__thumb {
transform: translateX(16px);
}

A13: Divider

Purpose: Visual separator between content sections

Test Criteria:

visual:
- horizontal: 100% width, 1px height
- vertical: 1px width, auto height
- color: gray-200

variants:
- solid: default line
- dashed: dashed border
- dotted: dotted border
- with_text: label in middle

accessibility:
- role: separator
- aria_orientation: horizontal/vertical
- decorative: aria-hidden if no semantic meaning

Variants:

<!-- Horizontal Divider (Default) -->
<hr class="divider" role="separator" data-testid="divider-horizontal" />

<!-- Vertical Divider -->
<div class="divider divider--vertical" role="separator" aria-orientation="vertical" data-testid="divider-vertical"></div>

<!-- Dashed Style -->
<hr class="divider divider--dashed" role="separator" />

<!-- Dotted Style -->
<hr class="divider divider--dotted" role="separator" />

<!-- With Text Label -->
<div class="divider-with-text" role="separator" data-testid="divider-text">
<span class="divider-with-text__label">or</span>
</div>

<!-- Thick Divider -->
<hr class="divider divider--thick" role="separator" />

<!-- Spacing Variants -->
<hr class="divider divider--spacing-sm" role="separator" />
<hr class="divider divider--spacing-lg" role="separator" />

CSS:

/* Horizontal divider */
.divider {
width: 100%;
height: 1px;
margin: var(--space-4) 0;
background: var(--color-gray-200);
border: none;
}

/* Vertical divider */
.divider--vertical {
width: 1px;
height: 100%;
min-height: 20px;
margin: 0 var(--space-4);
background: var(--color-gray-200);
}

/* Style variants */
.divider--dashed {
height: 0;
border: none;
border-top: 1px dashed var(--color-gray-300);
}

.divider--dotted {
height: 0;
border: none;
border-top: 1px dotted var(--color-gray-300);
}

.divider--thick {
height: 2px;
}

/* Spacing variants */
.divider--spacing-sm {
margin: var(--space-2) 0;
}

.divider--spacing-lg {
margin: var(--space-6) 0;
}

/* Divider with text */
.divider-with-text {
display: flex;
align-items: center;
gap: var(--space-3);
margin: var(--space-4) 0;
}

.divider-with-text::before,
.divider-with-text::after {
content: '';
flex: 1;
height: 1px;
background: var(--color-gray-200);
}

.divider-with-text__label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
}

/* Color variants */
.divider--primary {
background: var(--color-blue-500);
}

.divider--muted {
background: var(--color-gray-100);
}

Usage Examples:

<!-- Section separator -->
<section>
<h2>User Details</h2>
<!-- content -->
</section>

<hr class="divider" />

<section>
<h2>Account Settings</h2>
<!-- content -->
</section>

<!-- Toolbar separator -->
<div class="toolbar">
<button>Bold</button>
<button>Italic</button>
<div class="divider divider--vertical"></div>
<button>Link</button>
<button>Image</button>
</div>

<!-- Login form -->
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<button type="submit">Sign In</button>

<div class="divider-with-text">
<span class="divider-with-text__label">or</span>
</div>

<button type="button">Sign in with Google</button>
</form>

These complete all 13 atoms with full specifications, test criteria, variants, and implementation code. Ready for conversion to React components.