MVCSS stands for Modular View CSS. It’s a Sass-based CSS architecture for creating predictable and maintainable application style. Those familiar with SMACSS will find a lot of overlap.
- Classes only; no IDs. Why?
- Try to avoid nesting selectors inside other selectors. Why?
- Practice single responsibility: each component is in its own file, and never affects the styling of other components. Why?
- Alphabetize everything (most IDEs can map this to a button).
- Use camelCase for compound words; no underscores or hyphens (hyphens are explained below).
- Modular. Eliminates the domino effect of modifying styles with unintended side-effects. Also avoids the
!important
turf war altogether. - Scalable. Works for even the most overly-complicated applications.
- Consistent. On a team using MVCSS, you can pick up right where someone else left off.
- Responsive. Works great on mobile-first responsive applications.
- Reactive. Integrates seamlessly with Ember, Angular, React, and just about any other MVC front end framework out there.
- Agnostic. Assumes nothing about how you want to design your application, only about how you organize your CSS.
- Tiny. With no bloat, it’s as small as it can be. Ultimately your size is up to you, but we consistently see 20–30KB total for all CSS using MVCSS + minification + gzip.
- More time up front. You have to style much more out-of-the box than other frameworks. The payoff in MVCSS is in the long-term: saving you from heavy refactoring down the line. But
- Lots of naming. Expect to be naming lots of things, and the burden of making sure your names are future-proof is on you.
- Defining modules can be hard. We don’t give you rules on what a module can/can’t be. This is great for flexibility, but can be difficult during build knowing what is and what is not a module. And sometimes refactoring of entire modules is not uncommon as you build or as your app evolves.
- No JavaScript plugins. There are no JS components made for MVCSS specifically, but you’re free to use just about any library/ies you’d like (even Bootstrap).
- No community (for now). Let’s face it—we haven’t evangelized it and documented it as much as we should, but we’re hoping that’ll change.
Note: the following examples are all in the bracket-less .sass
format, but apply just the same to the more common .scss
format.
.module
.module-submodule
.module--modifier
.has-module
.is-[something]
The root module. Usually contains submodules (see “Submodule” below), and also may be modified (see “Modifier” below).
.footer
background-color: darkslategrey
color: white
Use submodules when you have a complicated module that needs additional styling hooks. This is better practice than habitually nesting selectors or elements within your Sass. In your HTML, the submodules don’t always have to be nested directly under the root module, but submodules still contribute to the module as a whole and typically don’t stand on their own. In extreme scenarios you may require sub- submodules—.module-submodule-submodule
—but this is rarely necessary and more often than not is an indicator you should refactor.
<article class="article">
<h1 class="article-title"></h1>
<time class="article-date"></time>
<div class="article-body"></div>
</article>
Modifies the root module. Should never @extend
the root, and shouldn’t repeat properties. This keeps your CSS minimal. Append to the root class in HTML instead: <div class="module module--modifier">
.
.btn
background-color: red
color: white
.btn--blue
background-color: blue
<button class="btn">I am a red button!</button>
<button class="btn btn--blue">I am a blue button!</button>
Every now and then you’ll need a container for a module. Single responsibility requires modules to handle their own styles 100%, and the context class exists for this purpose. The context class is always named .has-[moduleName]
.
If your root module was .alert
that had to stick to the top of the <body>
element:
.alert
left: 0
position: absolute
right: 0
top: 0
.has-alert
position: relative
<body class="has-alert">
<div class="alert"></div>
…
This allows you to style the alert without polluting the global styling of your body
tag.
State classes are useful for user interactions. States can represent which navigation element is current (.is-current
), which carousel item is visible (.is-visible
), or even which element is animating (is-animating
). State classes can be named anything, but always start with .is-
and end in a one-word description of what that element is doing. States are always attached to a modifier class so 1) your states are always scoped to your module and never bleed into others (single-responsibility), and 2) so states always take priority.
.notification
background-color: red
display: none
.notification.is-visible
display: block
We recommend keeping your files organized in a particular order: base
> submodules
> modifiers
> context
> states
. This way you can stick to single-class specificity and still have the styles cascade properly.
Here is an example structures/_cal.sass
(CSS attributes removed for clarity):
// *************************************
//
// Calendar
// -> User-selectable date picker
//
// *************************************
// -------------------------------------
// Base
// -------------------------------------
.cal
// -------------------------------------
// Submodules
// -------------------------------------
// ----- Day ----- //
.cal-day
// ----- Month ----- //
.cal-month
// ----- Year ----- //
.cal-year
// -------------------------------------
// Modifiers
// -------------------------------------
// ----- Large ----- //
.cal--l
// ----- Small ----- //
.cal--s
// -------------------------------------
// Context
// -------------------------------------
.has-cal
// -------------------------------------
// States
// -------------------------------------
// ----- Disabled ----- //
.cal.is-disabled
// ----- Showing ----- //
.cal.is-showing
Note that everything is alphabetized within each section. Make sure to do the same with CSS attributes. As for comment style, feel free to experiment, but this is a very readable style.
The application.sass
file—a convention from Rails—is the main manifest where everything is loaded in the correct order with @import
, and will eventually be compressed and served as a single file. The folder structure is as follows:
application.sass
foundation/
components/
structures/
And opening each folder you’ll find:
application.sass
foundation/
└ reset
└ helpers
└ config
└ base
└ tools
components/
└ [your components]
structures/
└ [your structures]
The MVCSS foundation in application.sass
is loaded in a specific order:
@import "foundation/reset"
@import "foundation/helpers"
@import "foundation/config"
@import "foundation/base"
And, lastly, "@import "foundation/tools"
is loaded at the end of application.sass
—after the components and structures. This way, the tools can do what they’re meant to do: override styles for one-off situations.
Reset — This is simply normalize.css.
Helpers — Sass functions to use throughout the app.
Config — Colors, typography, responsive settings, grid configuration, and anything else that could benefit from a Sass variable within the app.
Base — Base styles for class-less DOM elements.
Tools — Reusable, single-attribute utility classes useful when making a module would be overkill (we use things like .ttu
for text-transform: uppercase
).
Generic CSS modules that are the scaffolding of the website (examples include .grid
for responsive grids, .modal
for modals, and .card
for card-like components). Ideally serve a multitude of purposes across an app.
Very styled CSS modules that cater to very specific parts of the app (such as .product
for styling an ecommerce product listing or .article
for styling a blog article). Generally serve one and only one purpose within an app.
Adding new modules is as simple as making a new .sass
file and placing it in the components/
or structures/
directory based on what it is. Some projects’ application.sass
uses the following:
@import "structures/*"
In this scenario, the file is added as soon as it’s placed in a folder. In this scenario, take care to delete unused styles so that the stylesheet doesn’t become bloated.
In other scenarios, you may see this instead:
@import "structures/bucket"
@import "structures/button"
@import "structures/cal"
…
In this scenario, you’ll need to add your file to either the components/
or structures/
folder and manually include it in application.sass
.
You can read the full documentation on MVCSS here.