## Modern CSS

Modern web applications are built on top of rich tooling for dealing with CSS, relying on plenty of NPM packages and build steps. A vanilla web application has to choose a lighter weight path, abandoning the preprocessed modern CSS approaches and choosing strategies that are browser native.

#

## Reset

Resetting the styles to a cross-browser common baseline is standard practice in web development, and vanilla web apps are no different.

A minimal reset is the one used by this site:

Other options, in increasing order of complexity:

modern-normalize

  A more comprehensive solution for resetting CSS for modern browsers.

  Include it from CDN

Kraken

  A starting point for front-end projects. It includes a CSS reset, typography, a grid, and other conveniences.

  Include it from CDN

Pico CSS

  A complete starter kit for styling of semantic HTML that includes a CSS reset.

  Include it from CDN

Tailwind

  If you're going to be using Tailwind anyway, you may as well lean on its CSS reset.

  Include it from CDN

#

## Fonts

Typography is the keystone of a web site or web application. A lean approach like vanilla web development should be matched with a lean approach for typography. Modern Font Stacks describes a varied selection of commonly available fonts with good fallbacks, avoiding the need to load custom fonts and add external dependencies.

This site uses the Geometric Humanist stack for normal text, and the Monospace Code stack for source code.

#

## The Toolbox

In any real world web project the amount of CSS quickly becomes unwieldy unless it is well-structured. Let's look at the toolbox that CSS provides us in modern browsers to provide that structure.

`@import`

  The most basic structuring technique is separating CSS into multiple files. We could add all those files in order as `<link>` tags into the `index.html` but this quickly becomes unworkable if we have multiple HTML pages. Instead it is better to `@import` them into the `index.css` where they will still download in parallel.

  For example, here's the main CSS file for this site:

  Below is a recommended organization of CSS files.

Custom properties (variables)

  CSS variables can be used to define the site's font and theme in a central place.

  For example, here are the variables for this site:

  CSS variables become even more capable when combined with calc().

Custom elements

  Styles can be easily scoped to a custom element's tag.

  For example, the styles of the avatar component from the components page are all prepended by the `x-avatar` selector:

  Custom elements can also have custom attributes that selectors can leverage, as with the `[size=lg]` style in this example.

Shadow DOM

  Adding a shadow DOM to a web component further isolates its styles from the rest of the page. For example, the `x-header` component from the previous page styles its `h1` element inside its CSS, without affecting the containing page or the header's child elements.

  All CSS files that need to apply to the shadow DOM must be loaded into it explicitly, but CSS variables are passed into the shadow DOM.

  A limitation of shadow DOMs is that to use custom fonts inside them, they must first be loaded into the light DOM.

#

## Files

There are many ways to organize CSS files in a repository, but this is the one used here:

`/index.css`
  The root CSS file that imports all the other ones using `@import`.
`/styles/reset.css`
  The reset stylesheet is the first thing imported.
`/styles/variables.css`
  All CSS variables are defined in this separate file, including the font system.
`/styles/global.css`
  The global styles that apply across the web pages of the site.
`/components/example/example.css`
  All styles that aren't global are specific to a component, in a CSS file located next to the component's JS file.

#

## Scope

In order to avoid conflicting styles between pages and components we want to scope styles locally by default. There are two main ways of achieving that in vanilla web development.

### Prefixed selectors

For custom elements that don't have a shadow DOM we can prefix the styles with the tag of the custom element. For example, here's a simple web component that uses prefixed selectors to create a local scope:

#### Tip: CSS Nesting

If you want a cleaner syntax, and you are comfortable with the browser support, consider using CSS Nesting.

### Shadow DOM import

Custom elements that use a shadow DOM start out unstyled with a local scope, and all styles must be explicitly imported into them. Here is the prefixed example reworked to use a shadow DOM instead.

To reuse styles from the surrounding page inside the shadow DOM, consider these options:

- Common CSS files can be imported inside the shadow DOM using `<link>` tags or `@import`.
- CSS variables defined in the surrounding page can be referenced inside the shadow DOM's styles.
- For advanced shadow domination the `::part` pseudo-element can be used to expose an API for styling.

#

## Replacing CSS modules

The local scoping feature of CSS modules can be replaced by one of the scoping mechanisms described above. For instance, let's take the example of CSS modules from the Next.JS 14 documentation:

As a vanilla web component, this is what that looks like:

Because the shadow DOM does not inherit the page's styles, the `styles.css` must first import the styles that are shared between the page and the shadowed web component.

#

## Replacing PostCSS

Let's go over the main page of PostCSS to review its feature set.

Add vendor prefixes to CSS rules using values from Can I Use.
  Vendor prefixes are no longer needed for most use cases. The `:fullscreen` pseudo-class shown in the example now works across browsers unprefixed.
Convert modern CSS into something most browsers can understand.
  The modern CSS you want to use is most likely already supported. The `color: oklch()` rule shown in the example now works across browsers.
CSS Modules
  See the alternatives described in the previous section.
Enforce consistent conventions and avoid errors in your stylesheets with stylelint.
  The vscode-stylelint extension can be added into Visual Studio Code to get the same linting at develop time, without needing it to be baked into a build step.

Bottom line: Microsoft's dropping of support for IE11 combined with the continuing improvements of evergreen browsers has made PostCSS largely unnecessary.

#

## Replacing SASS

Similarly to PostCSS, let's go over the main feature set of SASS:

Variables
  Replaced by CSS custom properties.
Nesting
  CSS nesting is widely supported across major browsers.
Modules
  Can be approximated by a combination of `@import`, CSS variables, and the scoping mechanisms described above.
Mixins
  Regrettably the CSS mixins feature that will replace this is still in specification.
Operators
  In many cases can be replaced by the built-in calc() feature.

Bottom-line: SASS is a lot more powerful than PostCSS, and while many of its features have a vanilla alternative it is not as easy to replace entirely. YMMV whether the added complexity of the SASS preprocessor is worth the additional abilities.

## Up next

Learn about making vanilla sites with web components.

Make Sites