Skip to content

CSS Styles

Michael Potter edited this page Mar 24, 2023 · 11 revisions

Getting Started

👉 Hey you! Yeah, you! 👈

Before you get started working with CSS, set up your editor with Stylelint. And be sure to configure your code editor to fix linting errors on save.

Doing so provides you and your teammates with many benefits, including:

  • automatically validating and updating token values whenever they're updating
  • automatic CSS formatting
  • unified code standards

Would you like to contribute to our CSS code standards? Open a PR to update them, and be sure to explain the reasons for your changes in your PR description, so your colleagues can digest them.

Including styles in your components

For developer ergonomics we separate our CSS from our TS files. This declutters the TS file and helps with syntax highlighting. We then use lit-css to import and embed the styles into the component at build time. When making a new component that contains styles, the component should have a corresponding .css file with the identical name as its corresponding .ts file.

Example

rh-footer/
  rh-footer-block.ts
  rh-footer-block.css

rh-footer-block.ts

import style from './rh-footer-block.css';

export class RhFooterBlock extends LitElement {
  static readonly styles = style;
  ...
}

Sharing Stylesheets

We have observed that sharing stylesheets by importing them twice can cause problems when bundling both importers using esbuild. To avoid this problem, share stylesheets by refering to the base class' reference;

❌ Will Break the Bundle:

import baseStyles from './BaseStyles.css';
import styles from './rh-foo.css';

@customElement('rh-jazz-hands')
export class RhJazzHands extends BaseJazzHands {
  static readonly styles = [baseStyles, styles];
}

✅ Will Successfully Bundle:

import styles from './rh-foo.css';

@customElement('rh-jazz-hands')
export class RhJazzHands extends BaseJazzHands {
  static readonly styles = [...BaseJazzHands.styles, styles];
}

Defining CSS Variables

When writing component CSS, care must be taken to avoid preventing user customization. Use the cascade and the inheriting nature of custom properties to your users' advantage.

  • DO define variables as defaults, so that user preferences are respected
    #label {
      color: var(--rh-jazz-hands-label-color, hotpink);
    }
  • DON'T assign variables meant for user customization on the :host, as this will override user preferences
    :host {
      --rh-jazz-hands-label-color: hotpink;
    }
    
    #label {
      color: var(--rh-jazz-hands-label-color);
    }
  • DO assign private variables on the host, since they are not meant to be customized:
    :host {
      --_label-font-size: var(--rh-font-size-heading-sm, 1.5rem);
    }
    
    #label {
      font-size: var(--_label-font-size);
    }
  • ⚠️ AVOID needlessly defining private variables, use token values directly when needed.

Tokens and Naming Conventions

Red Hat Design Tokens' purpose is bring uniformity of style to the design system while making developers' jobs easier. Lean into it!

Choosing Variables Names

  • DO use existing token names wherever possible
  • ⚠️ AVOID exposing component-specific custom properties where design tokens already exist
    /* GOOD */
    border-radius: var(--rh-border-radius-pill, 80px);
    /* BAD */
    border-radius: var(--rh-jazz-hands-border-radius-pill, 80px);

If you must create a new name for your component:

  • DO use lower-dash-case
  • DO prefix the custom property with the element name
  • DON'T use double-dashes
  • DO extend from the general to the specific, left to right
  • ⚠️ AVOID abbreviations or acronyms
  • ⚠️ AVOID 'region' names, use slots and parts instead
    /* GOOD */
    background-color: var(--rh-jazz-hands-closed-background-color, transparent);
    /* BAD */
    background-color: var(--rh-c--jazz-hands__m-lg__BackgroundColor_____RedHat, red);

Private Variables

You may use "private custom properties" that are assigned to Shadow DOM elements. Assigning them to private elements in this way lets you do more in css and less in JavaScript while keeping your element's API surface small.

These "private" variables should begin with an underscore and should avoid including the custom element name.

<header>
  <button id="close-button">x</button>
  <slot name="header"></slot>
</header>
[part="header"] {
  --_offset: var(--rh-space-sm, 8px);
}

[part="header"].mobile {
  --_offset: var(--rh-space-xs, 4px);
}

#close-button {
  margin-inline-start: var(--_offset);
}

Lightdom CSS

While we encourage developers to style components using the built in scoping method, (i.e. using the styles class field) occasionally we need to include page level styles to target elements that we are unable to style from within the component. We refer to these as "lightdom styles" or "lightdom CSS".

Example

In rh-footer, we need to style the nested <li> and <a> tags within our component to maintain semantic lists. In web components, we are currently unable to style slotted elements that are not that direct children of a component using the ::slotted() sudo selector. To solve this we include a file called rh-footer-lightdom.css.

Scoping lightdom styles

Since lightdom styles are global to the page we need to scope our selectors to our component.

GOOD

rh-footer [slot^="links"] li {
  margin: 0;
  padding: 0;
  display: contents;
}

rh-footer [slot^="links"] a {
  display: block;
  color: var(--rh-color-text-primary-on-dark, #ffffff) !important;
  font-size: var(--rh-footer-link-font-size, var(--rh-font-size-body-text-sm, 0.875rem));
}

BAD

[slot^="links"] li {
  margin: 0;
  padding: 0;
  display: contents;
}

[slot^="links"] a {
  display: block;
  color: var(--rh-color-text-primary-on-dark, #ffffff) !important;
  font-size: var(--rh-footer-link-font-size, var(--rh-font-size-body-text-sm, 0.875rem));
}

Including a lightdom CSS file

By default, all .css files are not published in @rhds/elements. This is because the contents of those files are meant to be imported into the web component at build time (see "Including styles in your components" section above). To enable .css files as being able to be packaged add a -lightdom.css to the file name. We include the following publishing pattern in our package.json for this purpose.

{
  "files": [
    "elements/*/*-lightdom.css",
    ...
  ]
}

Make sure that you include instructions for adding this lightdom css file in the documentation for the component.