OOCSS (Object-Oriented CSS): The Complete Guide
OOCSS (Object-Oriented CSS) was created by Nicole Sullivan at Yahoo in 2008 — one of the first formal CSS methodologies. It introduced the idea of treating CSS components as reusable "objects" with separate structure and skin concerns. While less popular as a standalone methodology today, its two core principles are the bedrock of every modern CSS system including Tailwind, BEM, and ITCSS.
1. The Two Core Principles
OOCSS is built on exactly two rules:
Principle 1: Separate Structure from Skin
Structure = layout, sizing, positioning (what it looks like geometrically) Skin = colored decorations (colors, gradients, borders, shadows, fonts)
/* ❌ Anti-pattern: structure and skin coupled together */
.btn-blue {
/* Structure */
display: inline-flex;
padding: 0.75rem 1.5rem;
border-radius: 9999px;
/* Skin */
background: #4299e1;
color: white;
border: none;
}
.btn-red {
/* Structure: COPY-PASTED! */
display: inline-flex;
padding: 0.75rem 1.5rem;
border-radius: 9999px;
/* Skin: different */
background: #e53e3e;
color: white;
border: none;
}
/* ✅ OOCSS: structure in one class, skin in another */
/* Structure class (the "object") */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
border: 2px solid transparent;
border-radius: 9999px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
/* Skin classes (the "themes") */
.btn-blue { background: #4299e1; color: white; }
.btn-red { background: #e53e3e; color: white; }
.btn-green { background: #48bb78; color: white; }
.btn-outline { background: transparent; border-color: currentColor; }
.btn-ghost { background: transparent; color: var(--color-text-muted); }
<!-- Compose in HTML -->
<button class="btn btn-blue">Primary</button>
<button class="btn btn-red">Danger</button>
<button class="btn btn-outline btn-blue">Outline Blue</button>
Principle 2: Separate Container from Content
Elements should not depend on the container they're placed in. A heading looks the same regardless of whether it's in a sidebar, main content, or card.
/* ❌ Anti-pattern: content tied to container */
.sidebar h2 { font-size: 1.25rem; color: var(--color-primary); }
.main h2 { font-size: 2rem; color: var(--color-text); }
/* Now you can't reuse the same heading in both places */
/* ✅ OOCSS: container-independent classes */
.heading-lg { font-size: 2rem; font-weight: 700; line-height: 1.2; }
.heading-md { font-size: 1.5rem; font-weight: 700; line-height: 1.3; }
.heading-sm { font-size: 1.25rem; font-weight: 600; line-height: 1.4; }
.text-primary { color: var(--color-primary); }
.text-muted { color: var(--color-text-muted); }
<aside class="sidebar">
<h2 class="heading-sm text-primary">Sidebar Heading</h2>
</aside>
<main>
<h2 class="heading-lg">Main Content Heading</h2>
</main>
2. The Media Object — OOCSS's Most Famous Pattern
Nicole Sullivan introduced the media object as OOCSS's archetypal reusable object. It's the pattern of an image (or media) alongside a text block — used in comments, tweets, user cards, notifications, and more.
<!-- The media object -->
<div class="media">
<div class="media__figure">
<img src="avatar.jpg" alt="User" class="media__image">
</div>
<div class="media__body">
<h4 class="media__title">Nicole Sullivan</h4>
<p class="media__text">This is a comment or notification text.</p>
</div>
</div>
<!-- Reversed: media on the right -->
<div class="media media--reverse">
<div class="media__figure">
<img src="avatar.jpg" alt="User" class="media__image">
</div>
<div class="media__body">
<p>Text on left, image on right.</p>
</div>
</div>
/* The Media Object — the original OOCSS component */
.media {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.media--reverse {
flex-direction: row-reverse;
}
.media--center {
align-items: center;
}
.media__figure { flex-shrink: 0; }
.media__image {
display: block;
border-radius: var(--radius-full); /* Circular avatar by default */
}
.media__body {
flex: 1;
min-width: 0; /* Prevents flex overflow */
}
.media__title {
font-weight: 700;
margin-bottom: 0.25rem;
color: var(--color-text);
}
.media__text {
color: var(--color-text-muted);
line-height: 1.6;
}
/* Skin layers — applied alongside .media */
.media--sm .media__image { width: 32px; height: 32px; }
.media--md .media__image { width: 48px; height: 48px; }
.media--lg .media__image { width: 64px; height: 64px; }
3. Building Reusable OOCSS Objects
The Box Object
/* Structure: the box object */
.box {
display: block;
padding: var(--space-6);
overflow: hidden;
}
/* Skins: visual treatments */
.box-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
}
.box-glass {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.08);
border-radius: var(--radius-lg);
backdrop-filter: blur(20px);
}
.box-highlight {
background: rgba(108, 99, 255, 0.08);
border: 1px solid rgba(108, 99, 255, 0.2);
border-left: 4px solid var(--color-primary);
border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.box-dark {
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
}
The Stack Object
A vertical layout object for spacing children consistently:
/* The stack object: consistent vertical spacing */
.stack { display: flex; flex-direction: column; }
/* Skins: gap sizes */
.stack-sm { gap: 0.5rem; }
.stack-md { gap: 1rem; }
.stack-lg { gap: 2rem; }
.stack-xl { gap: 4rem; }
The Cluster Object
Groups of inline items that wrap:
/* The cluster: wrapping inline group */
.cluster {
display: flex;
flex-wrap: wrap;
align-items: center;
}
/* Skins */
.cluster-sm { gap: 0.5rem; }
.cluster-md { gap: 1rem; }
.cluster-lg { gap: 1.5rem; }
.cluster-left { justify-content: flex-start; }
.cluster-center { justify-content: center; }
.cluster-right { justify-content: flex-end; }
The Grid Object
/* The grid object */
.grid-layout { display: grid; }
/* Skins */
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
.grid-auto-fill { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.grid-gap-sm { gap: 1rem; }
.grid-gap-md { gap: 1.5rem; }
.grid-gap-lg { gap: 2rem; }
4. OOCSS Typography Objects
/* Structure: typographic scale */
.text-hero { font-size: clamp(2.5rem, 5vw + 1rem, 5rem); line-height: 1.1; }
.text-display { font-size: clamp(2rem, 4vw, 3.5rem); line-height: 1.15; }
.text-h1 { font-size: clamp(1.75rem, 3vw, 2.5rem); line-height: 1.2; }
.text-h2 { font-size: clamp(1.5rem, 2.5vw, 2rem); line-height: 1.25; }
.text-h3 { font-size: clamp(1.25rem, 2vw, 1.5rem); line-height: 1.3; }
.text-xl { font-size: 1.25rem; line-height: 1.4; }
.text-base { font-size: 1rem; line-height: 1.6; }
.text-sm { font-size: 0.875rem; line-height: 1.5; }
.text-xs { font-size: 0.75rem; line-height: 1.4; }
/* Skin: weights */
.weight-normal { font-weight: 400; }
.weight-medium { font-weight: 500; }
.weight-semibold { font-weight: 600; }
.weight-bold { font-weight: 700; }
.weight-black { font-weight: 900; }
/* Skin: colors */
.color-default { color: var(--color-text); }
.color-muted { color: var(--color-text-muted); }
.color-primary { color: var(--color-primary); }
.color-danger { color: var(--color-danger); }
.color-success { color: var(--color-success); }
/* Skin: families */
.font-heading { font-family: var(--font-heading); }
.font-body { font-family: var(--font-body); }
.font-mono { font-family: var(--font-mono); }
5. OOCSS Alert / Notification Object
<div class="alert alert-success">
<span class="alert__icon">✓</span>
<p class="alert__message">Your changes have been saved successfully.</p>
<button class="alert__close" aria-label="Close">×</button>
</div>
<div class="alert alert-danger">
<span class="alert__icon">⚠</span>
<p class="alert__message">Please fix the errors before submitting.</p>
</div>
<div class="alert alert-info">
<span class="alert__icon">ℹ</span>
<p class="alert__message">Your account is pending verification.</p>
</div>
/* Structure: alert object */
.alert {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem 1.25rem;
border-radius: var(--radius-md);
border-left: 4px solid transparent;
font-size: var(--text-sm);
line-height: 1.5;
}
.alert__icon {
flex-shrink: 0;
font-size: 1rem;
margin-top: 0.1rem;
}
.alert__message {
flex: 1;
margin: 0;
}
.alert__close {
flex-shrink: 0;
background: none;
border: none;
cursor: pointer;
font-size: 1.25rem;
line-height: 1;
opacity: 0.6;
transition: opacity 0.2s;
}
.alert__close:hover { opacity: 1; }
/* Skin: color variants */
.alert-success {
background: rgba(56, 161, 105, 0.1);
border-left-color: var(--color-success, #38a169);
color: #68d391;
}
.alert-success .alert__icon { color: var(--color-success); }
.alert-danger {
background: rgba(229, 62, 62, 0.1);
border-left-color: var(--color-danger, #e53e3e);
color: #fc8181;
}
.alert-danger .alert__icon { color: var(--color-danger); }
.alert-warning {
background: rgba(237, 137, 54, 0.1);
border-left-color: var(--color-warning, #ed8936);
color: #f6ad55;
}
.alert-warning .alert__icon { color: var(--color-warning); }
.alert-info {
background: rgba(108, 99, 255, 0.1);
border-left-color: var(--color-primary, #6c63ff);
color: #a78bfa;
}
.alert-info .alert__icon { color: var(--color-primary); }
6. OOCSS Badge / Tag Object
<span class="badge">Default</span>
<span class="badge badge-primary">Primary</span>
<span class="badge badge-success">Active</span>
<span class="badge badge-danger">Error</span>
<span class="badge badge-outline badge-primary">Outline</span>
<span class="badge badge-sm badge-success">Small</span>
<span class="badge badge-lg badge-primary">Large</span>
/* Structure: badge object */
.badge {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.25rem 0.625rem;
border-radius: var(--radius-full);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
border: 1px solid transparent;
line-height: 1;
white-space: nowrap;
}
/* Size skins */
.badge-sm { font-size: 0.65rem; padding: 0.15rem 0.5rem; }
.badge-lg { font-size: 0.875rem; padding: 0.375rem 0.875rem; }
/* Color skins */
.badge-primary { background: rgba(108,99,255,0.15); color: var(--color-primary); border-color: rgba(108,99,255,0.3); }
.badge-success { background: rgba(56,161,105,0.15); color: var(--color-success); border-color: rgba(56,161,105,0.3); }
.badge-danger { background: rgba(229,62,62,0.15); color: var(--color-danger); border-color: rgba(229,62,62,0.3); }
.badge-warning { background: rgba(237,137,54,0.15); color: var(--color-warning); border-color: rgba(237,137,54,0.3); }
.badge-neutral { background: rgba(255,255,255,0.08); color: var(--color-text-muted); border-color: var(--color-border); }
/* Outline skin modifier */
.badge-outline { background: transparent; }
7. OOCSS in WordPress
Applying OOCSS principles in WordPress child theme development:
/* ── Structural object: container ── */
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: clamp(1rem, 4vw, 2rem);
padding-right: clamp(1rem, 4vw, 2rem);
}
/* Skin: max widths */
.container-sm { max-width: 640px; }
.container-md { max-width: 960px; }
.container-lg { max-width: 1200px; }
.container-xl { max-width: 1440px; }
/* ── Structural object: section ── */
.section { display: block; }
/* Skin: vertical spacing */
.section-sm { padding-block: 3rem; }
.section-md { padding-block: 5rem; }
.section-lg { padding-block: 8rem; }
/* Skin: backgrounds */
.section-dark { background: var(--color-bg); }
.section-surface { background: var(--color-surface); }
.section-primary { background: var(--color-primary); color: white; }
/* ── Override GeneratePress with OOCSS objects ── */
.site-main .entry-content > * {
max-width: 72ch; /* Container skin on GP content */
}
8. OOCSS vs BEM vs Utility-First
| Aspect | OOCSS | BEM | Utility-First |
|---|---|---|---|
| Core unit | Object (reusable) | Block (component) | Single declaration |
| HTML verbosity | Medium | Low | High |
| CSS file size | Small (reuse objects) | Medium | Very small (no custom) |
| Learning curve | Low | Medium | Low |
| Team scalability | Good | Excellent | Good with framework |
| Specificity risk | Low (flat) | Low (flat) | Very low |
| WordPress fit | Good | Excellent | Requires build step |
| Prototyping speed | Medium | Medium | Fast |
| Maintainability | Good | Excellent | Good |
OOCSS is best for:
- Projects with many visual variations of the same component type
- Teams that want the simplicity of utilities but with more semantic grouping
- Libraries and design systems where objects are the currency
- Working alongside Sass
@extendor@mixinpatterns
9. OOCSS File Organization
css/
├── objects/
│ ├── _media.css ← Media object
│ ├── _box.css ← Box object
│ ├── _stack.css ← Stack object
│ ├── _cluster.css ← Cluster object
│ ├── _grid.css ← Grid object
│ └── _container.css ← Container object
├── skins/
│ ├── _colors.css ← Color skin utilities
│ ├── _typography.css ← Text skin utilities
│ ├── _spacing.css ← Spacing skin utilities
│ └── _surfaces.css ← Background/border skins
├── base/
│ ├── _reset.css
│ └── _variables.css
└── main.css
10. OOCSS Quick Reference
Two Principles:
1. Structure ≠ Skin — Separate layout from decoration
2. Container ≠ Content — Elements don't depend on where they live
Object Structure classes: define box model, layout, positioning
Skin classes: define colors, gradients, borders, shadows
Composition: <div class="[OBJECT] [SKIN] [SKIN]">
Example: <div class="btn btn-primary btn-lg">Text</div>
<div class="box box-card box-spacious">Content</div>
<div class="badge badge-success badge-sm">Active</div>
DO: Keep structure classes size/color-neutral
DO: Skin classes ONLY add decoration — never change size
DON'T: Use descendant selectors: .card h2 { } (ties content to container)
DON'T: Name classes after visual appearance: .red-box (use .box + .box-danger)