ryanfiller89@gmail.com

What?

An idea that’s always popular in the design system community is writing “self contained” components that are able to dynamically adapt to their parents. Container queries and container units have been getting a lot of buzz lately as they get closer to browser support, but that’s not quite what I’m talking about.

Complex child selectors have been around probably as long as CSS has existed, but the ability to make selections based on parent components is something that does not exist (yet). However, you can use child selectors along with Svelte’s native scoping to apply styles based its HTML parents.

Why?

Consider a basic website with a<header> and a <footer>, both of which contain a <nav> with a list of the site’s pages and subpages. For design reasons, the two <nav>s are styled differently, but for semantic reasons they contain similar markup.

a very basic site showing a <header> and <footer> element, with a <nav> element styled differently within each
This represents like... 80% of every website I ever worked on when I was at a marketing agency.

The design system could contain both a <HeaderNav> and <FooterNav>, require passing a prop to denote where the <Nav> is being used, or something really complicated like use the contextAPI. The layout of the <Nav> could be the responsibility of the <Header> and <Footer> to define, but that kind of breaks the encapsulation rules of component systems.

How?

This “trick” is based on one of my favorite SCSS features, but Svelte provides a cleaner way to do something similar with no additional configuration.

Svelte does some really helpful style magic. Styles within a component’s <style> block will be scoped to that component by means of appending a unique cssHash to the selector and associated class name.

<style>
  .nav { ... }

  .ul { ... }
</style>

<nav class="nav">
  <ul class="ul">
    ...
  </ul>
</nav>

Svelte also comes with an escape hatch from this scoping convention in the :global() modifier. By wrapping a selector in :global() that CSS block can escape its component and apply to any other component rendered on the page.

<style>
  :global(.nav) { ... }

  :global(.ul) { ... }
</style>

<nav class="nav">
  <ul class="ul">
    ...
  </ul>
</nav>

This method combines these two ideas within one component. A <style> block can reach outside its own component using :global(), and also apply a descendant selector of a hashed class to keep the styles specific.

<style>
  .nav { ... }

  :global(header) .nav { ... }

  :global(footer) .nav { ... }
</style>

<nav class="nav">
  ...
</nav>

And that’s it! Without having to install any additional plugins, you can use Svelte to make components that react differently given different parents. If the :global() selector is used anywhere in the components parent tree, the contextual styles apply.

If you want to explore this more in action, I made a quick REPL on svelte.dev that uses actual components to build the example from the screenshot above.