CSS is Awesome

Authoring icons

Icons in css-is-awesome are inline SVGs tinted by currentColor, so every glyph re-skins with the active theme automatically — no icon font, no sprite sheet, no runtime tint pipeline.

Approach

The icon system is folder-based and mixin-first. Drop an SVG into public/icons/, reference it with @include m.svg(name), and it inherits the parent's color. The same file is also usable as a plain inline <svg> in React components — that is the pattern the docs site itself follows. The principles are the same either way:

Using icons

There is no <Icon> React component in the docs site — components inline the SVG directly (see src/components/SearchBar/SearchBar.tsx for the canonical example). The same pattern works in any app. From SCSS, the mixin API in scss/_icons.scss is the primary surface:

// SCSS — inline icon that tints with text color
@use 'cia/scss/mixins' as m;

.btn-close {
  @include m.svg(close);   // resolves to /icons/x.svg via alias
  color: var(--color-danger);
}

.btn-save {
  @include m.svg-text(check);  // icon + label, 0.5em gap
}

.logo {
  @include m.svg-bg(brand-mark, 48px); // multi-color, no tint
}

From React, inline the SVG and let currentColor do the work. This is exactly how the real SearchBar component renders its magnifier:

// Decorative: aria-hidden, inherits parent color
<span aria-hidden="true">
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
       stroke-width="1.6" width="24" height="24">
    <circle cx="10.5" cy="10.5" r="7.5" />
    <path d="M16 16l5 5" />
  </svg>
</span>

Authoring a new icon

  1. Start with a 24×24 viewBox. Every icon in public/icons/ uses viewBox="0 0 24 24". Matching the existing grid keeps strokes and optical weight consistent across the pack.
  2. Use fill="none", stroke="currentColor", stroke-width="1.6". The shipped icons use strokes between 1.6 and 1.8 with stroke-linecap="round" and stroke-linejoin="round". Stick to that palette unless you have a reason — mixed stroke weights look ragged in a toolbar.
  3. Keep paths minimal. Icons render at 16–24px most of the time. Decorative detail (fine hatching, thin highlights, micro curves) is lost below 24px. Collapse compound shapes to single paths where possible — the shipped check.svg is a single <path d="M4 12l5 5L20 6" />.
  4. Name icons by semantic role. Use check, close, chevron-down — not little-x or blue-arrow. When the visual metaphor changes, only the file changes; every call site stays correct. Use $theme-icon-svg-alias in scss/theme/_icons.scss when you want a semantic call site (delete) to resolve to a neutral filename (trash.svg) — the default map already ships delete → trash and close → x.
  5. Drop the file into public/icons/ and wire it in. No registry, no import list. From SCSS: @include m.svg(my-icon) now works. From React, import the raw SVG markup or copy/paste it inline. For a full authoring reference, see public/icons/README.md.

Sizing and color

Size is set at the call site via width / height (or the $size argument on the mixins). Color is never set on the SVG — it reads currentColor and follows whatever color the parent has. That is the entire coupling between icons and themes: when the theme picker changes the foreground color token, every icon on the page re-tints in the same frame.

// The SVG stroke is currentColor, so it inherits the button's ink.
// Re-skin via the theme picker — the icon re-colors on the same frame.
<button class="cia-btn-primary">
  <span aria-hidden="true">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
         stroke-width="1.8" width="16" height="16">
      <path d="M4 12l5 5L20 6" />
    </svg>
  </span>
  Save
</button>

If you need an icon that must not tint — a brand mark, a multi-color illustration — use m.svg-bg instead of m.svg. It renders the file as a plain background-image, preserving the baked-in palette.

Accessibility checklist

Third-party icon packs

css-is-awesome has no runtime dependency on a third-party pack — the shipped public/icons/*.svg are hand-authored. If your team already uses Heroicons, Lucide, Phosphor, or Feather, all four emit inline SVG with stroke="currentColor" and 24×24 viewBoxes, so they drop in with zero conversion. Import the React component the pack ships, or copy the raw SVG markup into your component and follow the conventions above. The SCSS layer also ships an opt-in Font Awesome integration (see m.fa-icon, m.fa-text, m.fa-spin in scss/_icons.scss) for teams already on a Font Awesome kit — it is disabled by default.

FA 6 Free is the default; flip $fa-pro: true in your theme config and fa-load emits Light / Thin / Duotone alongside Solid / Regular / Brands, with the family map switched to Font Awesome 6 Pro automatically.

// app.scss — turn Pro on once, fa-load + fa() both follow
@use 'css-is-awesome/scss/theme/icons' with ($fa-pro: true);
@use 'css-is-awesome/scss/main';
@use 'css-is-awesome/scss/icons' as i;

:root { @include i.fa-load; }
.note::before { @include i.fa(check, light); }

Drop the woff2 files for the weights you ship at $theme-fa-path (default /webfonts). Missing files don’t error — they just don’t render until you add them, so you can ramp up to the full Pro set incrementally.

Further reading

Theme