Skip to content

✨ A clean, readable, and pragmatic naming convention for CSS classes that balances structure with developer autonomy 🧠 πŸ’….

License

Notifications You must be signed in to change notification settings

mikezotovdev/fluffy-css

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Fluffy CSS Logo

Fluffy CSS Methodology

A clean, readable, and pragmatic naming convention methodology for CSS classes that balances structure with developer autonomy.

Finally, a CSS methodology that feels intuitive, scales naturally, and gives developers the structure they needβ€”without the overhead they dread.


Motivation

Fluffy CSS is ideal for individual developers and teams building modern web apps with component-based frameworks like Vue or React. It's designed to scale smoothly from small side projects to complex interfaces while keeping naming conventions simple and maintainable.

CSS methodologies like BEM or SMACSS are powerful but often feel verbose, rigid, or difficult to onboard. Fluffy CSS aims to offer:

  • Readable and aesthetically pleasing class names
  • Consistency across components without excessive boilerplate
  • Natural compatibility with component-based frameworks like Vue and React
  • Enough flexibility to avoid "methodology police" while keeping code structured and maintainable

Core Rules

  1. Class Names

    • Use only lowercase letters and dashes (-).
    • Class names must start with a letter.
    • Dashes must be surrounded by letters (e.g. foo-bar, not foo--bar or -active).
  2. Modifiers

    • Begin with is- or has- (e.g. is-disabled, has-footer).
    • Must be used alongside their base class (never standalone).
  3. Parent-Child Naming

    • Prefix child elements with their parent block name (e.g. profile-header-title).
    • If the child is conceptually independent or deeply nested, introduce a new name. A helpful signal for this is when the class name becomes excessively long β€” e.g. app-input-control-right-content-icon suggests a structure that's too deeply nested and tightly coupled. In such cases, extract a new conceptual name like right-content-icon to represent the subcomponent or utility clearly and cleanly.
  4. Global Classes

    • Use only single-word global classes (e.g. button).
    • Modifiers may be added to global classes and must follow modifier naming conventions (e.g. button is-secondary).
  5. Class Name Order

    • The order of classes matters for clarity and consistency.
    • Always place the element-specific class first, then global classes and their modifiers.
    • Example: modal-content-close-button button is-icon is preferred over button modal-content-close-button is-icon.
  6. Selectors

    • Use only class and pseudo-class selectors. Modifier selectors (e.g. .is-active) must never be used independently. Always scope them relative to their base class using SCSS nesting (e.g. &.is-active { ... }). This enforces a structural connection between the modifier and its parent component.
    • Avoid tag-based selectors except in controlled slots (e.g. strong inside content). For example, if your component has a slot for rich text, and you expect semantic tags like <strong>, it’s fine to style the strong tag directly without requiring a class. This avoids unnecessary markup and ensures consistent styling.

Additional Rules

These are highly recommended rules that influence how you should apply Fluffy CSS in real-world development:

  • Avoid deep nesting in structure; refactor into smaller components if needed.
  • Use SCSS nesting cautiously; avoid selectors deeper than 3 levels.
  • Avoid global utility classes (e.g. u-hidden, text-ellipsis). Instead, use SCSS mixins for common visual behaviors or patterns like text styles or media queries. This reduces cognitive load β€” you don’t need to remember global class names or wonder how they affect other parts of your app. Mixins apply styles directly in your local context, making class name clashing impossible and your components more predictable.
.product-card-description {
  @include utils.ellipsis;
}

.login-page-title {
  @include utils.text-h1;
}

These mixins offer the benefits of reusable logic without polluting global class space or breaking component encapsulation.


Common Sense Recommendations

These aren't strictly required by the methodology, but they help maintain consistency and developer happiness:

  • Name components with at least two words (e.g. SignUpForm, LoginPage).
  • The root class of the component should match the component name. For example, a DashboardPage component should have a top-level container with the class dashboard-page.
  • Prefix library components with App (e.g. AppInput, AppModal).

DO’S and DON’TS

🚫 DON’T: Use a modifier class without its base

<!-- ❌ Wrong -->
<span class="is-error">Required</span>

is-error is a modifier and should never appear without a base class.

<!-- βœ… Correct -->
<span class="form-error-message is-error">Required</span>

🚫 DON’T: Nest selectors more than 3 levels deep

// ❌ Too much nesting
.account-widget {
  .account-widget-header {
    .account-widget-header-logo {
      .account-widget-header-logo-icon {
        color: var(--color-success);
      }
    }

    .account-widget-header-title {
      strong {
        //...
      }
    }
  }
}

Deep nesting increases specificity and coupling. Split into several blocks.

// βœ… Better structure
.account-widget {
  //...
}

.account-widget-header {
  //...

  .account-widget-header-logo {
    //...
  }

  // No need in nesting inside `.account-widget-header-logo`,
  // plain structure is better here
  .account-widget-header-logo-icon {
    //...
  }

  .account-widget-header-title {
    //...

    strong {
      //...
    }
  }
}

🚫 DON’T: Chain modifiers without hierarchy

<!-- ❌ Wrong -->
<button class="is-primary is-rounded">Click me</button>

Modifiers should extend or describe a base class. Without one, they're meaningless.

<!-- βœ… Correct -->
<button class="action-button is-primary is-rounded">Click me</button>

🚫 DON’T: Use tag selectors in component styles

// ❌ Avoid this
button {
  color: var(--color-error);
}

Tag selectors should only be used in very specific, controlled slots (e.g. content with known HTML).

// βœ… Prefer class-based selectors
.app-form-cancel-button {
  color: var(--color-error);
}

🚫 DON’T: Abbreviate class names

<!-- ❌ Avoid cryptic names -->
<div class="prfl-hdr-ttl"></div>

Class names should be self-explanatory. Prefer clarity over brevity.

<!-- βœ… Clear and semantic -->
<div class="profile-header-title"></div>

Examples

This section showcases how to apply Fluffy CSS in real-world components, ranging from simple building blocks to more complex layouts. The examples are written in Vue for clarity and structure, but the naming conventions and methodology principles easily translate to React, Svelte, or any other component-based framework. Use these as inspiration or references when adopting Fluffy CSS in your own projects.

πŸ‘€ Profile Badge

<template>
  <div class="profile-badge">
    <img
      class="profile-badge-avatar"
      :src="avatarUrl"
    />
    <span class="profile-badge-name">{{ fullName }}</span>
  </div>
</template>

<style lang="scss" scoped>
  @use '@/styles/utils' as utils;

  .profile-badge {
    //...

    .profile-badge-avatar {
      //...
    }

    .profile-badge-name {
      //...
      @include utils.text-caption;
    }
  }
</style>

βš™οΈ Settings Section

<template>
  <section class="settings-section">
    <h2 class="settings-section-title">Notifications</h2>
    <p class="settings-section-description">Manage your notification preferences</p>

    <AppCheckbox
      class="settings-notification-checkbox"
      label="Email Alerts"
      :is-checked="isEmailEnabled"
    />
  </section>
</template>

<style scoped lang="scss">
  @use '@/styles/utils' as utils;

  .settings-section {
    //...

    .settings-section-title {
      //...
      @include utils.text-h2;
    }

    .settings-section-description {
      //...
      @include utils.text-description;
    }

    .settings-notification-checkbox {
      //...
    }
  }
</style>

πŸ“Š Dashboard Overview

<template>
  <div class="dashboard-page">
    <div class="dashboard-page-header">
      <AppIcon 
        class="dashboard-page-header-icon"
        :name="AppIconName.STATUS"
      />
      <h1 class="dashboard-page-header-title">Dashboard</h1>
      <p class="dashboard-page-header-description">Displays common statistics</p>
    </div>

    <div class="dashboard-page-content">
      <DashboardPageStatistics class="dashboard-page-content-statistics" />
      <DashboardPageBalances class="dashboard-page-content-balances" />
    </div>
  </div>
</template>

<style lang="scss" scoped>
  @use '@/styles/utils' as utils;
  
  .dashboard-page {
    //...
  }
  
  .dashboard-page-header {
    //...

    .dashboard-page-header-icon {
      //...
    }

    .dashboard-page-header-title {
      //...
      @include utils.text-h1;
    }
  
    .dashboard-page-header-description {
      //...
      @include utils.text-description;
    }
  }

  .dashboard-page-content {
    //...
  }
</style>

πŸ›οΈ Product Card

<template>
  <div
    class="product-card"
    :class="{ 'has-footer': hasFooter }"
  >
    <img
      class="product-card-image"
      :src="image"
    />
    <div class="product-card-info">
      <h3 class="product-card-info-title is-sale">{{ title }}</h3>
      <p class="product-card-info-description">{{ description }}</p>
      
      <ProductCardPrice
        class="product-card-info-price"
        :price="price"
      />
    </div>
    <div
      v-if="hasFooter"
      class="product-card-footer"
    >
      <ProductCardSaleBadge :type="saleType" />
    </div>
  </div>
</template>

<style lang="scss" scoped>
  @use '@/styles/utils' as utils;

  .product-card {
    //...

    &.has-footer {
      //...
    }

    .product-card-image {
      //...
    }
  }

  .product-card-info {
    //...

    .product-card-info-title {
      //...
      @include utils.text-h3;

      &.is-sale {
        //...
      }
    }

    .product-card-info-description {
      //...
      @include utils.ellipsis;
    }

    .product-card-info-price {
      //...
    }
  }

  .product-card-footer {
    //...
  }
</style>

πŸ”˜ AppSwitch Toggle

<template>
  <label
    class="app-switch"
    :class="{ 'is-disabled': isDisabled }"
  >
    <input
      class="app-switch-input"
      type="checkbox"
    >
    <span
      class="app-switch-slider"
      :class="{ 'is-checked': isChecked }"
    ></span>
  </label>
</template>

<style lang="scss" scoped>
  .app-switch {
    //...

    &.is-disabled {
      //...
    }
  }

  .app-switch-input {
    //...

    &:focus-visible {
      // That nesting is pretty deep, that's why
      // the `.app-switch-input` selector is moved to the root
      ~ .app-switch-slider {
        //...
      }
    }
  }

  .app-switch-slider {
    //...

    &:after {
      //...
    }

    &.is-checked {
      //...

      &:after {
        //...
      }
    }
  }
</style>

πŸ’¬ Comment Thread

<template>
  <section class="comment-thread">
    <h3 class="comment-thread-title">Comments</h3>

    <ul class="comment-thread-list">
      <li
        v-for="comment in comments"
        :key="comment.id"
        class="comment-thread-list-item"
      >
        <div class="comment-item">
          <div class="comment-item-header">
            <span class=comment-item-header-author">{{ comment.author }}</span>
            <span class=comment-item-header-date">{{ comment.date }}</span>
          </div>

          <p class="comment-item-message">
            {{ comment.message }}
          </p>

          <div class="comment-item-footer">
            <button
              class="comment-item-footer-reply-button button is-tertiary"
              type="button"
            >
              Reply
            </button>
            <span class="comment-item-footer-likes">{{ comment.likes }} likes</span>
          </div>
        </div>
      </li>
    </ul>
  </section>
</template>

<style lang="scss" scoped>
  @use '@/styles/utils' as utils;

  .comment-thread {
    //...

    .comment-thread-title {
      //...
      @include utils.text-header;
    }
  }

  .comment-thread-list {
    //...

    .comment-thread-list-item {
      //...
    }
  }

  .comment-item {
    //...

    .comment-item-message {
      //...
    }
  }

  .comment-item-header {
    //...

    .comment-item-header-author {
      //...
    }

    .comment-item-header-date {
      //...
    }
  }

  .comment-item-footer {
    //...

    .comment-item-footer-reply-button {
      //...
    }

    .comment-item-footer-likes {
      //...
    }
  }
</style>

πŸͺŸ AppModal Layout

<template>
  <div class="app-modal">
    <div class="app-modal-backdrop"></div>
    <div class="app-modal-scroll-area">
      <div
        class="modal-content"
        :class="{ 'is-small': isSmall, 'is-medium': isMedium, 'is-huge': isHuge }"
      >
        <button
          class="modal-content-close-button button is-icon is-tertiary"
          type="button"
        >
          <AppIcon :name="AppIconName.CLOSE" />
        </button>

        <form class="modal-content-main">
          <div class="modal-header">
            <h2 class="modal-header-title">
              <slot name="title"></slot>
            </h2>

            <div class="modal-header-description">
              <slot name="description"></slot>
            </div>
          </div>
           
          <div class="modal-content-main-body">
            <slot></slot>
          </div>
           
          <div
            class="modal-content-footer"
            :class="{ 'is-left': isFooterAlignedLeft }"
          >
            <slot name="footer">
              <button
                class="modal-content-footer-cancel-button button"
                :class="{ 'is-error': isError, 'is-tertiary is-large': isTablet }"
                type="button"
              >
                <slot name="cancel-button-text">
                  Cancel
                </slot>
              </button>
              <button
                class="modal-content-footer-confirm-button button"
                :class="{ 'is-error': isError }"
                type="button"
              >
                <slot name="confirm-button-text">
                  Confirm
                </slot>
              </button>
            </slot>
          </div>
        </form>

        <div class="modal-content-aside">
          <slot name="aside"></slot>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
  @use '@/styles/utils' as utils;

  .app-modal {
    //...

    .app-modal-backdrop {
      //...
    }

    .app-modal-scroll-area {
      //...
    }
  }

  .modal-header {
    //...

    .modal-header-icon {
      //...
    }

    .modal-header-title {
      //...
    }
  }

  .modal-content {
    //...

    @include utils.tablet {
      //...
    }

    &.is-small {
      //...
    }

    &.is-medium {
      //...
    }

    &.is-huge {
      //...
    }

    .modal-content-close-button {
      //...
    }

    .modal-content-main {
      //...

      @include utils.tablet {
        //...
      }
    }

    .modal-content-aside {
      //...
    }

    .modal-content-footer {
      //...

      &.is-left {
        //...
      }

      @include utils.tablet {
        //...
      }
    }
  }
</style>

Comparison with Other Methodologies

Feature Fluffy CSS BEM SMACSS RSCSS Atomic CSS
Readability High Medium Medium High Low
Naming Verbosity Moderate High Moderate Low Low
Modifier Syntax is-*, has-* --* is-* _modifier Utility-first
Learning curve Low Medium Medium Low Medium

Links


πŸ™‹β€β™‚οΈ Author

Created by Mike Zotov

LinkedIn Β· GitHub


❀️ Support

If you find this project helpful, feel free to support it:

  • ERC-20/BNB/Base: 0x4e45bc70931bdcbf9afebb193fce04cf76703016
  • Solana: J3xH9qfGvz4UBmdyS4JvLka4zzSK8ui6yugkdqDwDEtq
  • Bitcoin: bc1qkmm4na0fmv4lgwgtftqwdsfzfpfpv7zqulft5f

πŸ“¦ License

MIT Β© Mike Zotov


About

✨ A clean, readable, and pragmatic naming convention for CSS classes that balances structure with developer autonomy 🧠 πŸ’….

Topics

Resources

License

Stars

Watchers

Forks