Introduction
CSS is an unusual language which can easily lead to code bloat, inconsistencies in design, and/or clashing code techniques. It is easy to end up with CSS code that is so fragile it can cause site-wide regressions with small changes.
- CSS should be valid
- No deprecated or non-standard code
- CSS should maintain a separation of concerns
- Do not apply styling and scripting with the same selector
- CSS should be accessible
- Avoid setting the pointer-events property to none on non-SVG elements
- The use of pointer-events for non-SVG elements is experimental
- Users can still interact with the element if navigating using a keyboard, and there is no communication to assistive technology that the element is disabled
- Avoid setting the pointer-events property to none on non-SVG elements
- CSS should be DRY
- Avoid duplication in the same file and across multiple files
- Reuse existing global, component, and utility classes before writing custom classes
- Do not redefine or override global styles or component styles
- Do not redefine or delete utility classes
- Do not restate default property-value combinations, e.g.: display: block; on block-level elements
- Do not redefine the line-height property value — the value of this property is set globally to meet accessibility requirements, and it should not be overridden
- Avoid writing code specific to an OS, browser, locale, or language
- CSS should have the lowest specificity possible
- Use class selectors to apply styling, rather than ID or element selectors
- Classes are reusable and have lower specificity
- IDs can only be used once per page and have higher specificity
- Use ID selectors as hooks for JavaScript and/or automated testing frameworks
- Use class selectors to apply styling, rather than ID or element selectors
- CSS should be maintainable
- When it makes sense, use variables to semantically abstract styles in order to make future rebrand efforts easier and seamless, e.g.: Do
border-color: $border-color-ghost;
Don'tborder-color: #ffffff;
- Add space to the top of elements in order to keep vertical spacing predictable while writing modular, component-based CSS
- Avoid setting fixed heights and widths
- When debugging, start by attempting to delete code before adding more
- Use a preprocessor and linter when possible — we recommend Sass and Stylelint
- When it makes sense, use variables to semantically abstract styles in order to make future rebrand efforts easier and seamless, e.g.:
Specificity
Use the minimum specificity required to achieve the desired styling. For more information on CSS specificity, check out Specificity Wars and Batificity.
!important
The !important declaration fundamentally destroys the specificity feature and can even break accessibility for some users. There is almost always another way to achieve the same goal without using the !important declaration. Treat the !important declaration like the nuclear option, only to be used in the most extreme cases, like when overriding styling from an external library that is using the !important declaration in its code.
In the rare case that using the !important declaration is absolutely necessary, add a comment in the code explaining why it is the only viable solution.
Inline styles
Do not hard code styles into your HTML, either with the style attribute or with deprecated attributes such as align, border, or width. Inline styles violate the separation of concerns and DRY principles, make CSS difficult to maintain and scale, and introduce inconsistencies within design systems. They also negatively impact specificity, accessibility, SEO, and performance.
Selectors
CSS is most maintainable with the simplest selectors. The longer the selector, the higher the specificity. The higher the specificity, the harder the CSS becomes to maintain.
Element and descendant selector chains that are tightly coupled to the HTML structure cause specificity issues, are not reusable, and are fragile because they can quickly become obsolete and result in breaking changes.
When writing selectors:
- Use the minimum specificity required to achieve the desired style
- Avoid nesting whenever possible
- Avoid unnecessary ancestor selectors, e.g.: Do
.btn-assertive
Don'tbutton.button-assertive
ID selectors
Do not use ID selectors to apply styling. The ID selector is the most specific selector since it can only match one element. Styling should be applied using a class selector instead. ID selectors, if used at all, should be used only as access points for JavaScript or automated testing frameworks.
Class selectors
Use class selectors to apply styling whenever possible.
Element selectors
Avoid overriding the default styles of an element. Element selectors should only be used in global stylesheets to define the application’s defaults, e.g.: body { background: $neutral-lightest; }
Attribute selectors
Avoid using several attribute selectors on commonly occurring components. Browser performance is known to be impacted by these.
Naming conventions
Semantics
- All variables, class names, and IDs should have semantic meaning
- Semantic names describe what an element is or does, rather than its current presentation
- What an element looks like (its presentation) can change across devices (responsive design) or over time (rebranding efforts)
- Semantic names remain logical even if the presentation changes
- .nav-primary and .nav-secondary instead of .nav-top and .nav-left
- .btn-assertive instead of .btn-blue
- $font-family-base-regular instead of 'SST Roman'
- .bsds-visually-hidden instead of a one-off custom class
- Avoid excessive and arbitrary shorthand, e.g.: .btn is useful for buttons, but .s is meaningless
Variables
- CSS variable names must be abstract and reusable
- Color palette variables:
- $neutral-darkest: #00000;
- $text-color-primary: $neutral-darkest;
- Utilities variables:
- $icon-sort-up: "\f0d8";
- $icon-sort-down: "\f0d7";
Classes
Component classes
- Use a parent-child / last-name-first approach to naming
- Prefix classes for nested elements with the name of the parent/component, e.g.: .card, .card-image, .card-content, .card-content-heading, .card-content-body
- This technique allows selectors to be bundled together when using a preprocessor, e.g.: .btn { ... &-assertive { ... } }
State modifier classes
- State modifier classes describe the state of a specific component or element
- State modifier classes should never appear on their own — they should always be scoped to a specific component or element via class chaining
- State modifier classes should be treated as a boolean, e.g.: true or false
- Consider using is-* prefix, e.g.: .btn.is-enabled, .btn.is-disabled, .form-control.is-valid, .form-control.is-invalid, .accordion.is-expanded, .accordion.is-collapsed
Utility classes
- Utility classes epitomize the single responsibility principle
- Utility classes apply a single rule or a very simple and universal pattern, without side effects
- Utility classes can be applied to any element, in any context, at any time
- Utility classes must always do exactly the same thing, so utility class declarations must never change
- Use sparingly — if you notice patterns, consider creating or modifying component classes
- Consider using u-* prefix, e.g.: .u-mx-auto, .u-p-0
JavaScript hooks
- Use only for JavaScript hooks — do not attach any styling
- Keep classes for JavaScript hooks out of CSS
- JavaScript hooks are event- or action-based, so consider using verbs when naming
- Use js-* prefix, e.g.: .js-toggle, .js-edit, .js-save, .js-delete
- The js-* prefix prevents unintentional breaking changes by informing future developers to look for uses of the class in JavaScript files rather than stylesheets
Formatting
In order to increase readability, please adhere to the following formatting standards.
- Include a leading 0 for property values and color parameters, e.g.: 0.5 instead of .5
- When setting property and value declarations for less than four sides of an element, shorthand should not be used. If a box only needs a bottom margin, use the margin-bottom property. This ensures that other property values will not be overwritten, and we can easily identify which side the property is being applied to without much cognitive effort, e.g.: Do
margin-bottom: 1rem;
Don'tmargin: 0 0 1rem 0;
- Mark TODOs and action items with a TODO comment and a ticket number
Organization
- Large stylesheets should be broken down into several smaller stylesheets, e.g.: global styles, page-level styles, and component-level styles
- CSS rulesets should be ordered to match the order of the source code
Declaration order
Our declaration order is set in our linter's config. New features that are not yet used in our codebase may not have a prescribed place, but likely would fit into one of the categories in a logical manner. CSS is evolving, and our standards will evolve with it.
Media queries
- Viewport width
- Start with the smallest breakpoint and work upwards — XS styles are default
- Keep media queries grouped at the bottom of the style sheet
- Only override what is necessary — do not redefine styles that do not change
- Animation
- Use the prefers-reduced-motion media feature to detect the end user’s preferences for non-essential motion and unset animations
Nesting
Avoid unnecessary nesting. Just because you can nest, doesn't mean you always should. HTML and CSS structure changes frequently. Nested parent-child styles can quickly become obsolete and result in breaking changes. Nesting also increases specificity. When nesting, nest sparingly and intentionally. Reassess any nesting more than 2 levels deep in order to prevent overly-specific selectors.