Combining the Powers of SEM and BIO for Improving CSS

By Everett Quebral
Picture of the author
Published on
image alt attribute

Combining the Powers of SEM and BIO for Improving CSS

Managing CSS in large applications can be difficult without a well-defined structure. Global styles leak, naming becomes inconsistent, and overrides get out of control. To tame the chaos, many developers rely on methodologies like BEM, OOCSS, ITCSS, and utility-first systems like Tailwind CSS.

In this article, we explore a practical hybrid approach: combining Semantic CSS (SEM) and BIO (Block-Intent-Override) naming patterns to bring clarity, reusability, and structure to modern styling strategies.

We’ll cover:

  • What SEM and BIO are and how they work
  • Benefits of combining semantic and structural CSS
  • Real-world examples in plain CSS and React
  • How this approach complements Tailwind or CSS Modules
  • Blog references and design system insights

What is SEM (Semantic CSS)?

Semantic CSS refers to using meaningful class names based on the component’s role, not its appearance. This improves communication between designers and developers and supports accessibility and content clarity.

Example:

<!-- ❌ Anti-pattern -->
<div class="blue-box center-text">Save successful!</div>

<!-- ✅ Semantic alternative -->
<div class="alert-message success">Save successful!</div>

By using names like alert-message, you tie class meaning to what the element does, not how it looks.

✅ SEM aligns with HTML5 semantics (<nav>, <article>, <section>) and works well in content-driven applications.

Key Benefits:

  • Promotes maintainable code
  • Avoids visual class churn (.text-blue-600, .rounded-lg, etc.)
  • Reduces cognitive load across large teams
  • Encouraged in design systems like GOV.UK Frontend

What is BIO (Block-Intent-Override)?

BIO stands for:

  • Block – The base component (.card, .alert)
  • Intent – The purpose or variation (.card--warning, .alert--success)
  • Override – State- or theme-based overrides (.card--dark .card__title)

This builds on principles from BEM but offers more flexibility by prioritizing behavioral intent over strict hierarchy.

/* Block */
.button {}

/* Intent */
.button--primary {}
.button--danger {}

/* Override */
.dark-theme .button--primary {
  background-color: #fff;
  color: #000;
}

Why Combine SEM + BIO?

Using Semantic CSS alone makes class names readable—but without variation control. Using BIO adds structured variations, state, and theme overrides while preserving meaning.

Together they allow you to:

  • Describe what a component is
  • Clarify why it appears that way
  • Customize how it behaves in different contexts

This method was explored in-depth by Max Böck in his post “On Class Naming” where he argues for role-based names + utility modifiers for clarity and scale.


Real-World Example: Notification Box

<div class="notification notification--error">
  <p class="notification__text">There was a problem submitting your form.</p>
</div>
.notification {
  padding: 1rem;
  border-radius: 0.25rem;
  font-size: 1rem;
}

.notification--error {
  background-color: #fee2e2;
  color: #b91c1c;
}

.notification--success {
  background-color: #dcfce7;
  color: #15803d;
}

.theme-dark .notification__text {
  color: #fefefe;
}

Here:

  • notification = semantic block
  • notification--error = intent
  • theme-dark .notification__text = override

This pattern is inspired by Harry Roberts' ITCSS structure, but simplified for component-driven projects.


Using SEM + BIO in React

Let’s implement this structure using a reusable React component.

interface NotificationProps {
  type: 'success' | 'error'
  darkMode?: boolean
  children: React.ReactNode
}

const Notification = ({ type, darkMode, children }: NotificationProps) => {
  return (
    <div className={`notification notification--${type} ${darkMode ? 'theme-dark' : ''}`}>
      <p className="notification__text">{children}</p>
    </div>
  )
}

✅ This design supports future extensions like .notification--warning or accessibility variants (.notification--aria).


How This Works with Tailwind CSS

Tailwind is utility-first, but semantic class wrappers still help for abstractions and contracts.

// tailwind.config.js
module.exports = {
  extend: {
    colors: {
      success: '#dcfce7',
      error: '#fee2e2'
    }
  }
}

Then:

<div class="notification bg-success text-green-800">All good!</div>

Or create utility wrappers:

.notification--success {
  @apply bg-green-100 text-green-800;
}

This creates a semantic contract on top of utilities—a key idea in tools like Tailwind UI.


Best Practices for SEM + BIO

✅ Use semantic base class names (.form-field, .modal-body, .tooltip-text)
✅ Apply intent classes for variations (--error, --primary)
✅ Create overrides for themes, responsive behavior, or accessibility
✅ Use a naming convention across your team or design system
✅ Co-locate CSS (or Tailwind abstractions) with components in modular folders


Final Thoughts

If you're struggling to balance semantic meaning with scalable variations, combining SEM and BIO offers a powerful middle ground.

This approach is:

  • ✅ Easy to read and reason about
  • ✅ Friendly for teams, designers, and accessibility tools
  • ✅ Compatible with any CSS system: Sass, Tailwind, CSS Modules, or PostCSS

By defining what a component is, why it’s styled that way, and how it changes in context, you’ll build a styling system that scales with your app and your team.


Resources

Stay Tuned

Want to become a Next.js pro?
The best articles, links and news related to web development delivered once a week to your inbox.