Skip to content

Why web components

Component-based development

Building sites with components has a lot of benefits for site visitors and the teams that run them.

How components work

Components are bite-sized sections of code that link together to create a website. They’re kind of like LEGO bricks. They’re often uniform and reusable.

Components help developers and designers save time. They help you avoid rewriting redundant code. You can also easily plug them into and remove them from code using HTML tags.

The California Design System uses the minimal amount of tech required for each component. This is often just HTML & CSS. When we need JavaScript, we take advantage of the component model built into the browser. We use the custom elements part of the web components specification. This allows us to define new HTML tags like <cagov-accordion> or <cagov-page-feedback>, which are inert until upgraded by provided JavaScript.

Examples of our components

The homepage also uses the feature card component. This component requires only HTML & CSS.

In some cases, we can embed components in other components. For example, we put a button in the feature card.

We use the page feedback component at the bottom of every page. This component requires HTML, CSS and JavaScript. It uses a custom element tag associated with a JavaScript class.

Web component advantages

Web components work anywhere

All framework support web components. You get this component model with its API built into all browsers. If you still need to support Internet Explorer, a polyfill is available. We use the @webcomponents/webcomponentsjs polyfill on

Web components help ensure longevity of code

Preferred web stacks evolve. Many mature organizations maintain applications written in a variety of frameworks. Each rewrite or launch is an opportunity to update codebases to the “modern approach,” but that changes over time.

Web components allow us to take our frontend widgets off this cycle of getting rebuilt in the newest framework flavor. Standardized features are built into all browsers. This means they’re supported forever. We are not subject to breaking changes in new versions of frameworks. We also do not worry about developers who prefer to write widgets that only work with a specific underlying technology.

Easier component management

Publishing each component individually lets us take advantage of separate versioning. Site owners can get dependabot notifications when there are updates to each npm package. This comes with associated semver compliant version numbers and published changelogs. Site owners can decide if they want to upgrade or ignore the change on their own timeline. Owners can add, upgrade, or remove components

more easily than if its code was part of the whole site. We learn a lot from other modular design systems like:

Components encapsulate code

The code for each widget is within the custom element class. Our component patterns with local variables protect us from accidentally influencing global variables. The custom element API provides a clear interface for interacting with the rest of the application. It receives events responding to instantiation, removal, and attribute updates.

When we use web components

We only need web components when we need JavaScript. We do not always need JavaScript. If HTML and CSS provide the desired features, that’s all a component uses.

Progressive enhancement

We cannot depend on every device supporting our web components, even if they work in the latest version of all browsers. Older devices get locked out of update cycles. Some people avoid freely available software updates.

Critical components should work even if the JavaScript fails. We recently refactored our accordions. Now they use <details> and <summary> elements. This lets people read the content, even if the JavaScript fails to execute. This failure state eliminates the extra styling or open/close animations.

If a component is not critical to a site’s mission, we skip progressive enhancement. We have not made the page feedback component progressively enhanced because it's not why people visit the page. That element is only visible when the visitor’s browser successfully executes its code. We also use <script type=module>, which is ignored by browsers that do not support ES6 components. In these cases, the network request is not even made. This stops the JavaScript from slowing down the basic experience.

Features of web components we use

Web components help limit development to the local scope of the custom element. There are good lifecycle tools for building up and tearing down JavaScript features. In other words, no waiting for "DOMContentLoaded" or any other old hacks.

We use plain custom elements. This does not stop us from depending on advanced base classes, but we get a lot of mileage out of the supported API now.

Applying CSS only to instantiated elements

We can use the :defined CSS selector to apply styling rules only when the custom element’s JavaScript has run.

For example we reference the custom element inside our CSS file with and without :defined:

cagov-accordion {
	/* these style rules are applied always */

cagov-accordion:defined {
	/* these style rules are applied only after the JavaScript code associated with this element has been run */

We import and deliver this CSS as part of the JavaScript package because it is only used in the js-powered version of the component.

Custom events

We often communicate between components with custom events. This is not a web component-specific tool, but it works nicely with any DOM element, including our own custom elements.

One example is our reusable county search box component on COVID-19 site pages. It fires events with county details when a visitor chooses from an autocomplete list. Chart components that need to use the county can subscribe to the custom event fired by the search custom element. They then update the data as needed.

Lots of other ways to communicate data between components

Web components provide lifecycle callbacks that respond to attribute changes. You can pass information in through attributes as strings. If you want to pass in a complex object, you can assign it to the custom element in the DOM via: document.querySelector("my-element").bigObject = myJSON;

You can refer to this inside the component via: this.bigObject

Custom elements can read their own interior HTML. We translate components server-side by writing translated HTML inside the custom element. Complex elements like charts need translated strings for things like tooltips and legends.

We use this pattern with our static site generator to provide these translations. 11ty builds a different version of each HTML page for each language we support. Then the charts on that page receive their translated strings in the page HTML.

Collaborate with us

Do you have an idea for a new component? Does your team already have a useful widget you think would be helpful on other sites? Let’s use web components to iterate together and refine reusable tech that works anywhere.

You can suggest changes or new components by opening issues in the open source Design System repository. If you have a component that’s built for a certain platform, we can work together to make it stack agnostic.