name: IBM Carbon Design System description: "IBM Carbon provides 30+ accessible, token-based components across React, Svelte, Angular, and Web Components. Use for enterprise B2B apps, data visualization dashboards, internal tools needing WCAG AA minimum compliance (often exceeds to AAA), and products requiring multiple theme support (White, Gray 10/90/100)."
IBM Carbon Design System
What Is Carbon?
IBM Carbon Design System is a mature, enterprise-grade design system emphasizing clarity, efficiency, consistency, and beauty. It's built on four core principles and serves as the foundation for IBM's suite of cloud products (IBM Cloud, Watson, hybrid cloud). With 30+ production components, 14 color tokens with theme variants, and strict accessibility standards (WCAG AA minimum, often AAA), Carbon powers interfaces that millions of users rely on daily. The design system is theme-agnostic—swap color tokens and entire interfaces adapt.
Core Design Principles
Clarity: Information hierarchy is obsessive. Grid-aligned content, consistent typography scale (12px to 32px), and semantic color use ensure users understand what matters. Never obscure information; make relationships explicit through proximity and weight.
Efficiency: Enterprise users value speed. Compact spacing (8px base), predictable component behavior, and keyboard-first interactions reduce cognitive load. Every interaction must serve a purpose—no decoration without function.
Consistency: Token-based design ensures pixel-perfect alignment across 50+ teams building with Carbon. Change a color token in one place; 1000 components update. This requires discipline: no hardcoded colors, no arbitrary spacing.
Beauty: Despite austere pragmatism, Carbon achieves beauty through restraint. Carefully-weighted typography, subtle elevation through shadows, and harmonious color palettes create interfaces that feel intentional and premium.
Visual Language
Color System (Multiple Themes)
Carbon provides four themes by switching a single token map:
White Theme (default, light background):
Primary: #0F62FE (IBM Blue)
Secondary: #525252 (Neutral Gray 9)
Interactive: #0043CE (Interactive Blue)
Hover: #0353E9 (Hover Blue)
Focus: #0F62FE with 2px border
Danger: #DA1E28 (Red)
Success: #24A148 (Green)
Warning: #F1C21B (Yellow)
Background: #FFFFFF (white)
Surface: #F4F4F4 (UI 01)
Gray 10 Theme (light gray backgrounds):
Same colors but Background: #F4F4F4
Surface: #E8E8E8 (darker than white theme)
Gray 90 Theme (dark mode, dark gray background):
Primary: #78A9FF (light blue)
Secondary: #E0E0E0 (light gray)
Background: #161616 (near-black)
Surface: #262626 (slightly lighter)
Text: #F4F4F4 (light gray, not pure white)
Gray 100 Theme (darkest, high contrast):
Primary: #A6C8FF (brightest blue)
Background: #0A0A0A
Surface: #1A1A1A
Text: #FFFFFF (pure white for max contrast)
Typography
- Font: IBM Plex Sans (open source, IBM's custom font)
- Fallback: -apple-system, Roboto, Segoe UI
- Scale:
- 12px / 16px line height: Caption, small labels
- 14px / 20px line height: Body (baseline standard)
- 16px / 24px line height: Productive heading
- 18px / 28px line height: Expressive heading
- 20px / 28px line height: Medium expressive
- 24px / 32px line height: Large expressive
- Weights: 400 (regular), 500 (semibold), 600 (bold)
Spacing Grid (16px base unit)
Carbon uses a 2x grid (16px base, supporting 8px increments):
- Compact: 8px (between related elements)
- Standard: 16px (between component sections)
- Spacious: 24px (between major sections)
- Section break: 32px (between distinct areas)
- Large: 40px, 48px, 56px, 64px (for major layout breaks)
Elevation & Shadows
- Raised (Level 0): No shadow, on background
- Floating (Level 1):
0 2px 4px rgba(0,0,0,0.1)— cards, dropdowns - Overlay (Level 2):
0 4px 8px rgba(0,0,0,0.15)— modals, floating panels - Modal (Level 3):
0 8px 16px rgba(0,0,0,0.20)— top-level overlays
Border Radius
- Sharp: 0px (no rounding)
- Small: 2px (small inputs, tight components)
- Standard: 4px (buttons, cards, most components)
- Large: 8px (large cards, panels)
Component Patterns
Button with Multiple Variants
import { Button } from 'carbon-components-react';
function ButtonShowcase() {
return (
<>
{/* Primary button - main action */}
<Button kind="primary">
Save Changes
</Button>
{/* Secondary button - alternative action */}
<Button kind="secondary">
Cancel
</Button>
{/* Tertiary button - less emphasis */}
<Button kind="tertiary">
Learn More
</Button>
{/* Ghost button - lowest emphasis */}
<Button kind="ghost">
Skip
</Button>
{/* Danger button - destructive action */}
<Button kind="danger">
Delete
</Button>
</>
);
}
<style>
/* CSS equivalent (Carbon components auto-generate these) */
.bx--btn--primary {
padding: 12px 16px;
background-color: #0F62FE;
color: #FFFFFF;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms cubic-bezier(0.2, 0, 0.38, 0.9);
}
.bx--btn--primary:hover {
background-color: #0353E9;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.bx--btn--primary:focus {
outline: 2px solid #0F62FE;
outline-offset: -4px;
}
.bx--btn--primary:active {
background-color: #0043CE;
}
</style>
Text Input with Label and Error State
import { TextInput, FormGroup, FormLabel } from 'carbon-components-react';
function SignUpForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const handleChange = (e) => {
const value = e.target.value;
setEmail(value);
if (value && !value.includes('@')) {
setError('Please enter a valid email');
} else {
setError('');
}
};
return (
<FormGroup>
<FormLabel htmlFor="email">Email Address</FormLabel>
<TextInput
id="email"
value={email}
onChange={handleChange}
placeholder="user@example.com"
invalid={!!error}
invalidText={error}
helperText="We'll never share your email"
/>
</FormGroup>
);
}
<style>
/* Carbon text input structure */
.bx--form-requirement {
font-size: 12px;
color: #8D8D8D;
margin-top: 4px;
}
.bx--form-requirement.error {
color: #DA1E28;
}
.bx--text-input {
padding: 0 16px;
height: 40px;
border-bottom: 1px solid #8D8D8D;
border-radius: 0; /* Carbon inputs have bottom border only */
font-size: 14px;
transition: border-color 150ms cubic-bezier(0.2, 0, 0.38, 0.9);
}
.bx--text-input:focus {
outline: none;
border-bottom-color: #0F62FE;
box-shadow: inset 0 -2px 0 #0F62FE;
}
.bx--text-input--invalid {
border-bottom-color: #DA1E28;
box-shadow: inset 0 -2px 0 #DA1E28;
}
</style>
Data Table with Actions
import {
DataTable,
Table,
TableHead,
TableRow,
TableHeader,
TableBody,
TableCell,
Button,
} from 'carbon-components-react';
function UsersTable() {
const headers = ['Name', 'Email', 'Status', 'Actions'];
const rows = [
{ id: '1', name: 'John Doe', email: 'john@example.com', status: 'Active' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', status: 'Pending' },
];
return (
<DataTable rows={rows} headers={headers}>
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
<Table {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader key={header.key} {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id} {...getRowProps({ row })}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>
{cell.value === 'Actions' ? (
<Button kind="ghost" size="small">Edit</Button>
) : (
cell.value
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)}
</DataTable>
);
}
<style>
/* Data table styling */
.bx--data-table-container {
border: 1px solid #E0E0E0;
border-radius: 4px;
overflow: hidden;
}
.bx--data-table {
width: 100%;
border-collapse: collapse;
}
.bx--table-header-label {
font-size: 12px;
font-weight: 600;
color: #161616;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.bx--table-row {
border-bottom: 1px solid #E0E0E0;
height: 48px;
}
.bx--table-row:hover {
background-color: #F4F4F4;
}
.bx--table-body--zebra .bx--table-row:nth-child(odd) {
background-color: #FAFAFA;
}
</style>
Code Generation Guidance
Token-First Approach: Never hardcode colors or spacing. Always derive from token map:
const carbonTokens = {
colors: {
primary: '#0F62FE',
interactive: '#0043CE',
danger: '#DA1E28',
success: '#24A148',
},
spacing: {
xs: '8px',
sm: '16px',
md: '24px',
lg: '32px',
},
};
const MyComponent = styled.button`
background-color: ${carbonTokens.colors.primary};
padding: ${carbonTokens.spacing.xs} ${carbonTokens.spacing.sm};
border-radius: 4px;
`;
Theme Switching: Carbon supports runtime theme switching. Provide a theme context:
const ThemeContext = React.createContext();
function ThemedApp() {
const [theme, setTheme] = useState('white'); // 'white' | 'gray10' | 'gray90' | 'gray100'
const themes = {
white: { bg: '#FFFFFF', text: '#161616', primary: '#0F62FE' },
gray90: { bg: '#161616', text: '#F4F4F4', primary: '#78A9FF' },
gray100: { bg: '#0A0A0A', text: '#FFFFFF', primary: '#A6C8FF' },
};
return (
<ThemeContext.Provider value={themes[theme]}>
<div style={{ background: themes[theme].bg, color: themes[theme].text }}>
{/* Components use theme tokens */}
</div>
</ThemeContext.Provider>
);
}
Grid Alignment Rule: Content aligns to 16px grid horizontally, 8px grid vertically (for tighter vertical rhythm).
.component {
padding: 16px; /* 1x grid unit */
margin-bottom: 8px; /* 0.5x grid unit for tight vertical spacing */
}
@media (max-width: 768px) {
.component {
padding: 16px; /* Maintain grid on mobile too */
margin-bottom: 16px; /* Increase to full grid unit on mobile */
}
}
Motion Standard: Carbon uses 150-200ms cubic-bezier(0.2, 0, 0.38, 0.9) for state changes (hover, focus, active). This easing feels natural and responsive without being overly dramatic.
Accessibility Notes
- WCAG AA Minimum (often exceeds): All color combinations meet 4.5:1 contrast ratio for text. Primary blue (#0F62FE) on white achieves 8.6:1.
- Focus indicators: 2px outline inset on buttons/inputs (visible at all zoom levels). Offset of 2px maximum.
- Touch targets: 44px × 44px minimum (Carbon uses 40px standard, acceptable for desktop).
- Keyboard navigation: All components support full keyboard access—Tab, Enter, Arrow keys, Escape.
- Screen readers: ARIA labels, roles, live regions provided on all interactive elements.
- Reduced motion: Replace transitions with instant state changes when
prefers-reduced-motionis set.
Examples
Example 1: Dashboard Card with Metrics
Input: Create a dashboard card showing system metrics (uptime, response time, error rate) with Carbon styling.
Output:
import { Card, Tile } from 'carbon-components-react';
function MetricsCard() {
return (
<Tile className="metrics-card">
<h3 style={{ margin: '0 0 24px 0', fontSize: '18px', fontWeight: '500' }}>
System Health
</h3>
<div className="metrics-grid">
<div className="metric">
<span className="metric-label">Uptime</span>
<span className="metric-value">99.95%</span>
<span className="metric-status success">Healthy</span>
</div>
<div className="metric">
<span className="metric-label">Response Time</span>
<span className="metric-value">245ms</span>
<span className="metric-status warning">Acceptable</span>
</div>
<div className="metric">
<span className="metric-label">Error Rate</span>
<span className="metric-value">0.02%</span>
<span className="metric-status success">Healthy</span>
</div>
</div>
</Tile>
);
}
<style>
.metrics-card {
background: #FFFFFF;
border: 1px solid #E0E0E0;
border-radius: 4px;
padding: 24px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 24px;
}
.metric {
display: flex;
flex-direction: column;
gap: 8px;
padding-bottom: 16px;
border-bottom: 1px solid #E8E8E8;
}
.metric-label {
font-size: 12px;
font-weight: 600;
color: #8D8D8D;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.metric-value {
font-size: 24px;
font-weight: 600;
color: #161616;
}
.metric-status {
font-size: 12px;
padding: 4px 8px;
border-radius: 2px;
width: fit-content;
}
.metric-status.success {
background: #D0E2D4;
color: #24A148;
}
.metric-status.warning {
background: #F9E3CA;
color: #B8860B;
}
</style>
Example 2: Modal Dialog with Form Validation
Input: Build a "Create Project" modal with form fields, validation feedback, and action buttons.
Output:
import { Modal, Button, TextInput, FormGroup, FormLabel } from 'carbon-components-react';
import { useState } from 'react';
function CreateProjectModal({ open, onClose }) {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [errors, setErrors] = useState({});
const handleCreate = () => {
const newErrors = {};
if (!name.trim()) newErrors.name = 'Project name is required';
if (name.length > 100) newErrors.name = 'Name must be under 100 characters';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
alert(`Creating project: ${name}`);
onClose();
};
return (
<Modal
modalHeading="Create New Project"
primaryButtonText="Create"
secondaryButtonText="Cancel"
open={open}
onRequestClose={onClose}
onRequestSubmit={handleCreate}
>
<FormGroup>
<FormLabel htmlFor="project-name">Project Name</FormLabel>
<TextInput
id="project-name"
value={name}
onChange={(e) => {
setName(e.target.value);
setErrors({ ...errors, name: '' });
}}
placeholder="My awesome project"
invalid={!!errors.name}
invalidText={errors.name}
/>
</FormGroup>
<FormGroup>
<FormLabel htmlFor="description">Description</FormLabel>
<TextInput
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="What is this project about?"
/>
</FormGroup>
</Modal>
);
}
function App() {
const [modalOpen, setModalOpen] = useState(false);
return (
<>
<Button onClick={() => setModalOpen(true)}>
Create Project
</Button>
<CreateProjectModal open={modalOpen} onClose={() => setModalOpen(false)} />
</>
);
}
When to use Carbon: IBM ecosystem products, enterprise B2B apps, cloud/data-heavy dashboards, internal tools requiring AA/AAA accessibility. Also great: teams wanting strict design system governance, multi-theme support. Avoid: consumer products (too enterprise), design-first startups (too rigid), mobile-only apps (desktop-optimized).