Skip to main content

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; }