CSS Naming Conventions and the Prefix System
Every serious CSS methodology uses a prefix system — a set of short, standard abbreviations attached to class names to communicate their role, origin, and layer at a glance. This page consolidates all prefix conventions in one reference and explains how to design your own consistent naming system.
1. Why Prefixes Matter
Without prefixes, you read a class name like .active and you have no idea:
- Is this a JavaScript state or a CSS modifier?
- Is it a layout class or a component class?
- Is it safe to delete?
- Will deleting a CSS class break JavaScript that relies on it?
Prefixes solve this at a glance:
<!-- Without prefixes — ambiguous -->
<div class="container sidebar open active hidden">
<!-- With prefixes — self-documenting -->
<div class="o-container l-sidebar is-open is-active u-hidden">
<!-- Object Layout JS State State Utility -->
2. The Complete Prefix Reference
Object Prefix: o-
Origin: ITCSS Layer 5 (Objects) Meaning: Structure-only, skin-neutral layout patterns
.o-container { max-width: 1200px; margin-inline: auto; }
.o-grid { display: grid; gap: 1.5rem; }
.o-stack { display: flex; flex-direction: column; }
.o-media { display: flex; align-items: flex-start; gap: 1rem; }
.o-cluster { display: flex; flex-wrap: wrap; gap: 1rem; }
.o-ratio { position: relative; overflow: hidden; }
.o-sidebar { display: flex; gap: 2rem; }
Rule: No colors, no borders, no shadows — structure only.
Component Prefix: c-
Origin: ITCSS Layer 6 (Components) Meaning: Fully styled, designed UI components
.c-btn { display: inline-flex; padding: 0.7rem 1.5rem; border-radius: var(--radius-full); }
.c-btn--primary { background: var(--color-primary); color: white; }
.c-card { background: var(--color-surface); border-radius: var(--radius-xl); }
.c-card__title { font-size: var(--text-xl); }
.c-nav { position: sticky; top: 0; z-index: var(--z-sticky); }
.c-hero { min-height: 100dvh; display: flex; align-items: center; }
.c-modal { position: fixed; inset: 0; z-index: var(--z-modal); }
.c-badge { display: inline-flex; padding: 0.2rem 0.6rem; border-radius: var(--radius-full); }
Rule: Components CAN have colors, shadows, borders. They're complete.
Utility Prefix: u-
Origin: ITCSS Layer 7 (Utilities)
Meaning: Single-purpose override class, high specificity, intentional !important
.u-hidden { display: none !important; }
.u-block { display: block !important; }
.u-flex { display: flex !important; }
.u-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.u-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.u-text-center { text-align: center !important; }
.u-text-right { text-align: right !important; }
.u-mt-0 { margin-top: 0 !important; }
.u-mb-0 { margin-bottom: 0 !important; }
.u-mx-auto { margin-inline: auto !important; }
.u-w-full { width: 100% !important; }
.u-min-w-0 { min-width: 0 !important; }
.u-overflow-hidden { overflow: hidden !important; }
.u-no-transition { transition: none !important; }
.u-no-animation { animation: none !important; }
.u-pointer-none { pointer-events: none !important; }
Rule: Always safe to apply. Never defines multiple properties. May use !important.
Layout Prefix: l-
Origin: SMACSS Layout Category Meaning: Major page structure sections — not reusable components
.l-page { min-height: 100dvh; display: flex; flex-direction: column; }
.l-header { position: sticky; top: 0; z-index: var(--z-sticky); }
.l-main { flex: 1; }
.l-footer { margin-top: auto; }
.l-sidebar { width: 280px; flex-shrink: 0; position: sticky; top: 5rem; }
.l-content { flex: 1; min-width: 0; }
.l-hero { min-height: 100dvh; }
.l-two-col { display: grid; grid-template-columns: 1fr 280px; gap: 3rem; }
.l-three-col { display: grid; grid-template-columns: 240px 1fr 220px; gap: 2rem; }
.l-full-width { width: 100%; max-width: 100%; }
Rule: One instance per page. Never reused across multiple pages as a component.
State Prefix: is- and has-
Origin: SMACSS State Category Meaning: Dynamic state toggled by JavaScript or user interaction
/* is- states: single-element condition */
.is-active { color: var(--color-primary) !important; }
.is-open { display: block !important; }
.is-closed { display: none !important; }
.is-hidden { visibility: hidden; pointer-events: none; }
.is-visible { visibility: visible; pointer-events: auto; }
.is-loading { opacity: 0.6; pointer-events: none; cursor: wait; }
.is-disabled { opacity: 0.4; pointer-events: none; cursor: not-allowed; }
.is-selected { background: rgba(108,99,255,0.1); border-color: var(--color-primary); }
.is-focused { border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(108,99,255,0.2); }
.is-sticky { position: fixed; top: 0; box-shadow: var(--shadow-md); }
.is-expanded { max-height: 9999px; overflow: visible; }
.is-collapsed { max-height: 0; overflow: hidden; }
.is-valid { border-color: var(--color-success); }
.is-invalid { border-color: var(--color-danger); }
.is-current { font-weight: 700; }
.is-dragging { opacity: 0.5; cursor: grabbing; }
/* has- states: parent reflects child's state */
.has-error { }
.has-error .c-form__input { border-color: var(--color-danger); }
.has-error .c-form__label { color: var(--color-danger); }
.has-error .c-form__message { display: block; color: var(--color-danger); }
.has-overlay { overflow: hidden; } /* body.has-overlay — when modal is open */
.has-sidebar { } /* .l-page.has-sidebar — triggers sidebar layout */
JavaScript Convention:
// SMACSS state pattern — JavaScript ONLY touches is- and has- classes
element.classList.add('is-loading');
element.classList.remove('is-loading');
element.classList.toggle('is-open');
// Never this (JavaScript creating pseudo-BEM modifiers):
element.classList.add('nav--open'); // ❌ BEM modifiers should be set in HTML, not JS
element.setAttribute('data-state', 'open'); // ✅ Alternative via data attributes
JavaScript Hook Prefix: js-
Origin: Community convention (not part of any official methodology) Meaning: Used ONLY by JavaScript as a hook — NEVER styled in CSS
<button class="c-btn c-btn--primary js-open-modal">
Open Modal
</button>
<form class="c-form js-contact-form" id="contact">
<input class="c-form__input js-email-input" type="email">
</form>
/* ❌ NEVER do this */
.js-open-modal { background: red; } /* JS hooks must have ZERO CSS */
.js-email-input { border: 1px solid blue; }
// ✅ JS uses js- hooks freely — deleting CSS never breaks selectors
document.querySelector('.js-open-modal').addEventListener('click', openModal);
document.querySelector('.js-contact-form').addEventListener('submit', handleSubmit);
// Benefit: Refactoring CSS (renaming .c-btn--primary) never breaks JS
// Benefit: Deleting js- class never affects appearance
The Rule: js- classes have zero CSS properties. They exist purely as stable DOM anchors for JavaScript.
Theme Prefix: t- or theme-
Origin: SMACSS Theme Category Meaning: Visual skin/color/typography layer applied to a section or whole page
.theme-dark { --color-bg: #0f0f0f; --color-text: #f0f0f0; }
.theme-light { --color-bg: #ffffff; --color-text: #1a1a1a; }
.theme-primary { --color-bg: var(--color-primary); --color-text: white; }
/* Applied inline for themed sections */
/* <section class="theme-dark"> applies dark tokens to all children */
.theme-dark .c-btn--outline { border-color: white; color: white; }
WordPress-Specific Prefixes
When working with WordPress, add a project prefix to avoid conflicts with themes, plugins, and Gutenberg:
/* Project prefix: my site abbreviation */
.gp-hero { } /* GeneratePress child theme */
.mgb-card { } /* My GenerateBlocks component */
.bb-feature { } /* Bricks Builder custom component */
.acme-nav { } /* Client project: Acme Corp */
/* Convention: [project]-[component]__[element]--[modifier] */
.acme-card { }
.acme-card__title { }
.acme-card--featured { }
3. The data-* Attribute Alternative
Some teams prefer data- attributes over is- state classes:
<!-- State via data attribute -->
<nav data-state="open">
<button data-loading="true">
<form data-valid="false">
<div data-theme="dark">
/* CSS targets data attributes */
[data-state="open"] { display: flex; }
[data-loading="true"] { opacity: 0.6; pointer-events: none; }
[data-valid="false"] input { border-color: var(--color-danger); }
[data-theme="dark"] { --color-bg: #0f0f0f; }
// JS toggles data attributes
el.dataset.state = 'open';
el.dataset.loading = 'true';
When to use data-* over is-:
- When state has multiple possible values (not just on/off)
- When integrating with server-side rendering that sets initial state in HTML
- When you want CSS to target states without JavaScript involvement
4. Complete Prefix Quick Reference
Prefix Origin CSS? JS? Description
──────────────────────────────────────────────────────────────
o- ITCSS ✅ Yes ❌ No Object: structure only, no colors
c- ITCSS ✅ Yes ❌ No Component: fully styled UI
u- ITCSS ✅ Yes ❌ No Utility: single-prop override
l- SMACSS ✅ Yes ❌ No Layout: major page sections
is- SMACSS ✅ Yes ✅ Adds State: dynamic, JS-toggled
has- SMACSS ✅ Yes ✅ Adds State: parent reflects child state
js- Community ❌ Never ✅ Yes JavaScript hook — ZERO CSS
theme-/t- SMACSS ✅ Yes Rarely Visual theme/skin layer
[project]- Custom ✅ Yes ❌ No Namespace for this project
BEM modifiers (--modifier) can coexist with all of the above
5. Designing Your Project's Prefix System
Step 1: Choose your base methodology
BEM-only project: .card, .card__title, .card--featured
ITCSS + BEM project: .c-card, .c-card__title, .c-card--featured
SMACSS + BEM project: .card, .nav (no prefix), .l-container, .is-active
WordPress project: .acme-card, .acme-card__title, .is-open
Step 2: Define your state convention
Option A: is- prefix (SMACSS): .is-active, .is-loading, .has-error
Option B: aria attributes (a11y): [aria-expanded="true"], [aria-hidden="true"]
Option C: data attributes: [data-state="open"], [data-loading]
Option D: combined: .is- for visual, aria for accessibility
Step 3: Define your JavaScript hooks
Option A: js- prefix: .js-toggle, .js-modal-trigger
Option B: data-action: [data-action="open-modal"]
Option C: ID selectors: #modal-trigger (for unique elements)
Step 4: Document in your project README
## CSS Class Conventions
| Prefix | Purpose | CSS? | JS? |
|--------|---------|------|-----|
| o- | Layout objects | Yes | No |
| c- | Components | Yes | No |
| u- | Utilities | Yes | No |
| l- | Page layout | Yes | No |
| is- | State (JS-toggled) | Yes | Add only |
| js- | JS hook | NEVER | Yes |
6. Anti-Patterns to Avoid
/* ❌ Generic state names without prefix — ambiguous */
.active { } /* Active what? */
.open { } /* Open what? */
.hidden { } /* Hidden from whom? */
/* ✅ Always prefix states */
.is-active { }
.is-open { }
.u-hidden { }
/* ❌ JS touching BEM modifiers */
// element.classList.add('card--loading'); ← BEM modifier from JS = confusion
/* ✅ JS only touches is- and js- */
// element.classList.add('is-loading');
/* ❌ Styling js- classes */
.js-submit-btn { background: red; } /* ← Fatal: breaks when CSS refactored */
/* ✅ js- ZERO CSS */
/* .js-submit-btn → no CSS properties ever */
/* ❌ Making utilities multi-property */
.u-card {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: var(--space-6);
}
/* ← This is a component, not a utility */
/* ✅ Utilities: ONE property per class */
.u-hidden { display: none !important; }
.u-rounded { border-radius: var(--radius-md) !important; }