Get started Layered modules

/anyblades/blades/tree/main/css/layered

Blades CSS, whole or part by part, is also available wrapped in @layer blades { }, so any unlayered styles of yours win over it without specificity fights.

Whole framework

blades.layered.css / blades.layered.min.css is blades.css wrapped in @layer blades { }:

<link rel="stylesheet" href="
  https://cdn.jsdelivr.net/npm/@anyblades/blades@3/css/layered/blades.layered.min.css
"/>

When you can use @import, @import url("…/blades.min.css") layer(blades); does the same without this file.

Per-part imports

With bundlers and component frameworks (SvelteKit, Next.js, Nuxt, Astro, …)

Import the parts from JavaScript, i.e. the root layout and the components that use them, rather than with @import inside a component's <style> block. The parts, @anyblades/blades/<part>, come wrapped in @layer blades, which a JS import can't add by itself.

// root layout, once: the base and every part that others build on
import "@anyblades/blades/variables";
import "@anyblades/blades/layout/document";
import "@anyblades/blades/content/typography";
import "@anyblades/blades/content/button";
import "@anyblades/blades/forms/basics";

// in the component that renders a <dialog>
import "@anyblades/blades/components/card";
import "@anyblades/blades/components/modal";
import "@anyblades/blades/components/modal-modern"; // optional: open animation without JS
<style>
  /* unlayered, so it wins over Blades whatever the specificity (except Blades' !important) */
  article {
    max-width: 40rem;
  }
</style>

Framework examples

A confirm dialog component, with the root layout importing the base parts as above. The component imports card and modal and overrides Blades in its own styles. Blades' dialog > article > footer button:not(:first-of-type) is more specific than the component's rules, but it is in @layer blades, so the component's rules win. With the non-layered source (@anyblades/blades/src/components/modal), the second button would keep Blades' margin-left.

Svelte, Confirm.svelte:

<script>
  import "@anyblades/blades/components/card";
  import "@anyblades/blades/components/modal";
</script>

<dialog open>
  <article>
    <p>Delete this item?</p>
    <footer>
      <button class="secondary">Cancel</button>
      <button>Delete</button>
    </footer>
  </article>
</dialog>

<style>
  footer {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
  }
  button {
    margin: 0;
  }
</style>

Vue (<script setup> and <style scoped>) and Astro (the imports in the frontmatter, <style> in the component) work the same way.

React, Confirm.jsx with a CSS module:

import "@anyblades/blades/components/card";
import "@anyblades/blades/components/modal";
import styles from "./Confirm.module.css";

export default function Confirm() {
  return (
    <dialog open>
      <article>
        <p>Delete this item?</p>
        <footer className={styles.footer}>
          <button className="secondary">Cancel</button>
          <button>Delete</button>
        </footer>
      </article>
    </dialog>
  );
}
/* Confirm.module.css */
.footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}
.footer button {
  margin: 0;
}

Per-page CSS in a SvelteKit production build of this example, next to the whole blades.layered.min.css (gzip sizes in parentheses):

CSS
blades.layered.min.css, for comparison 76.9 kB (12.4 kB)
Every page: the root layout's parts 25.5 kB (4.7 kB)
The page with Confirm: plus card, modal and the component's styles + 4.7 kB (+ 1.4 kB)

The sizes are from a build with build: { cssTarget: "esnext" } in vite.config.js. With Vite's default browser targets, light-dark() is lowered to fallback custom properties, which adds about 4 kB to the root layout's CSS.

Available parts

@anyblades/blades/<part> is the file under css/layered/:

Order and dependencies

Overrides and caveats

Override in your component's <style> or app.css, or set --pico-* variables on :root or on an element. Unlayered CSS always wins over @layer blades. The usual layer caveats apply: