Skip to main content
Component Gallery v4.2.0

SYX Design System

Ordered by layer hierarchy: utilities → layout → atoms → molecules → organisms.

7 layers
665 tokens
6 themes
WCAG AA
Zero dependencies

Authoring Guide

Practical decision rules for writing HTML and SCSS with SYX. Read this before creating any component.

Where does my code go? — Decision flowchart

Quick reference

Layer Prefix When to use Anti-pattern
Util .syx- Single CSS property: display, spacing, color, visibility Never add visual design to a utility class
Atom atom- Smallest reusable building block: btn, icon, label, pill, input Don't compose atoms inside atoms
Mol mol- Composition of 2+ atoms that always travel together If sub-parts make sense independently → separate atoms
Org org- Complete UI section; appears across multiple pages Don't put page-unique content inside an organism
Page Layout tweaks exclusive to one specific page Don't duplicate component logic in page-level styles

Rule of thumb: Utilities are adjectives (.syx-d-flex → the display is flex). Components are nouns (.mol-card → a card). If you're unsure between atom and molecule: would the sub-parts ever make sense independently? Yes → separate atoms. No → molecule.

Worked examples

atom "I need a button" → atom-btn
atom "Button with icon" → atom-btn + atom-icon composed in HTML, not a new mol-
mol "Search field" → mol-search (input + icon + btn always together)
"Site header" → org-site-header
util "Center a div on this page" → .syx-mx-auto or .syx-text-center

HTML Authoring

2.1 — Semantics first

Always start with the semantically correct element, then apply SYX classes. Never choose an element because of its default browser style.

✅ Correct

<nav aria-label="Main navigation" class="org-navbar">
  <ul class="org-navbar__list" role="list">
    <li><a href="/" class="atom-link">Home</a></li>
  </ul>
</nav>

❌ Wrong — div soup

<div class="org-navbar">
  <div class="org-navbar__list">
    <div><span class="atom-link">Home</span></div>
  </div>
</div>

2.2 — BEM in HTML

  • Block + modifier always together. Never write a modifier without the base block class: atom-btn atom-btn--primary ✅ — atom-btn--primary alone ❌
  • Max 2 BEM depth levels. If you need __element__sub, those are two independent elements.
  • Dynamic states → is-, not BEM modifiers. BEM modifiers are permanent variants; is-open, is-loading are JS-driven state.
<!-- ✅ Correct -->
<button class="atom-btn atom-btn--primary">          <!-- block + modifier -->
  <span class="atom-btn__icon" aria-hidden="true">   <!-- element -->
</button>
<button class="atom-btn is-loading">                <!-- dynamic state -->

2.3 — Compose in HTML, not in SCSS

Organisms and molecules are composed in HTML. Keep SCSS partials free of cross-layer selector references.

<!-- ✅ Composed in HTML — molecules inside organisms -->
<header class="org-header">
  <div class="org-header__inner layout-grid">
    <a href="/" class="atom-link org-header__logo">…</a>
    <nav class="org-navbar">…</nav>
    <div class="mol-btn-group">
      <button class="atom-btn atom-btn--ghost">Log in</button>
      <button class="atom-btn atom-btn--primary atom-btn--filled">Sign up</button>
    </div>
  </div>
</header>

<!-- ❌ Wrong — organism SCSS references a molecule selector -->
/* organisms/_header.scss */
.org-header .mol-btn-group { margin-left: auto; } ← coupling between layers

Valid exception: an organism CAN define layout-level gap or grid-template-areas for its direct children. What it cannot do is redefine the internal styles of a molecule.

2.4 — Accessibility (required)

Element Required attributes
<img> alt="" (decorative) or alt="description" (informative)
<button> icon-only aria-label="…" always
<a> icon-only aria-label="…" always
<nav> aria-label="…" to distinguish multiple navs
<section> aria-labelledby="id-heading" pointing to the section's <h*>
Interactive state aria-expanded, aria-current, aria-selected as appropriate
Decorative icons aria-hidden="true" on all decorative <span class="atom-icon">
<!-- ✅ Icon-only button done right -->
<button class="atom-btn atom-btn--ghost atom-btn--circle"
        aria-label="Close menu">
  <span class="atom-icon atom-icon--lc-x" aria-hidden="true"></span>
</button>

2.5 — Layout Grid

Use .layout-grid for all main content areas. Never invent ad-hoc flex containers for page structure.

<!-- ✅ Standard responsive layout -->
<main class="layout-grid">
  <div class="layout-grid__col-xs-12 layout-grid__col-md-8"><!-- main --></div>
  <aside class="layout-grid__col-xs-12 layout-grid__col-md-4"><!-- sidebar --></aside>
</main>

<!-- ✅ Full-bleed section with constrained inner content -->
<section class="org-hero">
  <div class="layout-grid">
    <div class="layout-grid__col-xs-12 layout-grid__col-md-8 layout-grid__col-lg-6">
      <h1 class="atom-title atom-title--h1">…</h1>
    </div>
  </div>
</section>

SCSS for efficient CSS output

Every SCSS decision carries a cost in the compiled CSS. These rules keep the output lean.

3.1 — Max 3 nesting levels

✅ Efficient — max 3 levels

.org-header {
  &__nav {         // level 2
    &--open { … } // level 3
  }
}
// Output: .org-header__nav--open {}

❌ Expensive — 5 levels

.org-header {
  &__nav {
    &__list {
      &__item {
        &--active { … }
// Output: .org-header__nav__list__item--active

3.2 — The @mixin wrapper pattern (mandatory)

Every component MUST be wrapped in a @mixin with a $theme parameter. This enables selective inclusion and multi-theme support.

// ✅ Correct
@mixin mol-card($theme: null) {
  @layer syx.molecules {
    .mol-card { /* base */ }
    .mol-card--featured { /* modifier */ }
  }
}

// Called from themes/example-01/setup.scss:
@include mol-card("example-01");

// ❌ Wrong — top-level rules cannot be excluded from any bundle
.mol-card { … }

3.3 — Null-safe shorthand

SYX mixins skip null values. Use this to avoid emitting unnecessary properties.

// ✅ Only emits padding-top and padding-bottom — no left/right
@include padding(var(--semantic-space-inset-md) null);

// ❌ Raw CSS always emits all 4 properties (sets left/right to 0)
padding: var(--semantic-space-inset-md) 0;

// ✅ Horizontal centering without touching top/bottom
@include margin(null auto);
// → margin-right: auto; margin-left: auto;

3.4 — Token → CSS output

SCSS CSS output Note
var(--component-btn-primary-bg) var(--component-btn-primary-bg) 1 property, runtime theming ✅
var(--primitive-color-blue-500) var(--primitive-color-blue-500) ❌ Skips semantic layer
#3b82f6 #3b82f6 ❌ Breaks theming
@include size(100%, 48px) width:100%;height:48px null-safe ✅
@include flex-center() display:flex;align-items:center;justify-content:center 3 props in 1 include ✅
@include transition(color 0.2s ease) transition:…; @media(prefers-reduced-motion){transition:none} Auto reduced-motion guard ✅

3.5 — Property order inside a rule

.syx-component {
  // 1. Positioning
  @include absolute($top: 0, $left: 0);

  // 2. Display / Box model
  @include flex-center();

  // 3. Dimensions
  @include size(100%, 48px);

  // 4. Spacing
  @include margin(null auto);
  @include padding(var(--component-x-inset-y) var(--component-x-inset-x));

  // 5. Typography
  font-size: var(--component-x-font-size);
  color: var(--component-x-color);

  // 6. Visual
  background-color: var(--component-x-bg);
  @include border(all, 1px, solid, var(--component-x-border));
  @include border-radius(var(--component-x-radius));
  box-shadow: var(--component-x-shadow);

  // 7. Transitions — ALWAYS before states
  @include transition(color 0.2s ease, background-color 0.2s ease);

  // 8. States
  &:hover { … }
  &:focus-visible { @include focus-ring(); }
  &:disabled { … }
  &--modifier { … }

  // 9. Elements (__element)
  &__icon { … }
  &__label { … }
}

Mixin Deep-Dive

Category Mixin Output / Note
Position @include absolute($top:0, $right:0) position:absolute;top:0;right:0 — null-safe
@include relative() position:relative — use even without coords
@include fixed($bottom:0) position:fixed;bottom:0
@include sticky($top:0) position:sticky;top:0
Spacing @include padding(Yval null) Only top+bottom — left/right not emitted
@include padding(Y X) Top+bottom = Y, Left+right = X
@include margin(null auto) Only margin-left+right: auto
Flex @include flex-center() display:flex;align-items:center;justify-content:center
@include flex-between() display:flex;align-items:center;justify-content:space-between
Transition @include transition(color 0.2s ease) Adds prefers-reduced-motion guard automatically ⭐
Media @include breakpoint(tablet) min-width: 50em — mobile-first
@include breakpoint(desktop) min-width: 70em
@include darkmode @media(prefers-color-scheme:dark)
@include min-screen(48em) Custom value — always em, never px
A11y @include sr-only() Visually hidden, accessible to screen readers
@include focus-ring() WCAG focus outline — use only inside :focus-visible
Text @include truncate(200px) Single-line ellipsis
@include ellipsis(3) Multi-line clamp to 3 lines
Border @include border(top, 1px, solid, var(--color)) Or all for all 4 sides
Size @include size(100%, 48px) width:100%;height:48px — null-safe
// ✅ Mobile-first with breakpoints
.org-hero__title {
  font-size: var(--semantic-font-size-xl);      // mobile

  @include breakpoint(tablet) {
    font-size: var(--semantic-font-size-2xl);   // tablet+
  }
  @include breakpoint(desktop) {
    font-size: var(--semantic-font-size-display); // desktop+
  }
}

// ✅ Focus ring only inside :focus-visible
&:focus-visible { @include focus-ring(); }

Tips, Patterns & Gotchas

1 The "wrong layer" trap

Utilities are adjectives. If you're inventing a visual utility, you're in the wrong layer.

❌ <div class="syx-card-featured"> → this is a molecule, not a utility
✅ <div class="mol-card mol-card--featured">

2 Don't use @include for single-property CSS

Mixins add value when they include null-safety or extra logic. For simple properties, use raw CSS.

❌ @include color(var(--component-x-color));  → doesn't exist, adds no value
✅ color: var(--component-x-color);
✅ opacity: 0.5;
✅ cursor: pointer;
✅ z-index: var(--semantic-z-index-dropdown);

3 Let @layer resolve specificity

Never increase specificity to win a style conflict. If a component isn't winning, it's a layer assignment problem.

❌ .org-header .atom-btn--primary { background: red; }  → forced specificity
✅ Use .syx-* utilities — they always win via @layer syx.utilities

4 Token without fallback → silent bug in other themes

If you define a token only in one theme's _theme.scss, the other themes inherit an empty value.

// ❌ Only in themes/example-02/_theme.scss:
--component-header-bg: hsl(0, 0%, 5%);  → themes 01,03,04,05 get no value

// ✅ Always define the fallback in abstracts/tokens/components/_header.scss:
--component-header-bg: var(--semantic-color-bg-primary); // default
// Then override only in the theme that needs it

5 $theme parameter flow

// setup.scss → passes the theme name to the mixin
@include org-header("example-02");

// organisms/_header.scss → three methods based on type of difference
@mixin org-header($theme: null) {
  // Method 1 — CSS token (automatic, all themes)
  background: var(--component-header-bg);

  // Method 2 — Sass map (structural differences)
  @if theme-cfg($theme, "header-sidenav-side", left) == right { right: 0; }

  // Method 3 — direct @if (1–2 themes only)
  @if $theme == "example-02" { backdrop-filter: blur(8px); }
}

6 PurgeCSS: protect dynamic JS classes

// postcss.config.js — safelist
safelist: [/^atom-btn--/, /^is-/];

// Or include class names in an HTML comment (PurgeCSS scans content):
<!-- is-open is-active is-loading atom-btn--danger -->

Quick decision checklist

Before writing any new code, ask yourself:

Question The answer determines…
Is this a reusable UI piece? Yes → component (atom/mol/org). No → page/util
How many HTML elements does it need? 1 → atom. 2–5 atoms → molecule. Full section → org
Does it need theme-aware colors/spacing? Yes → use component tokens → var(--component-*)
Will it appear on multiple pages? Yes → component layer. No → page layer
Is it a one-off CSS property tweak? Yes → utility class .syx-*
Does it need JavaScript interaction? Add a .js- hook class — never style .js- classes
Am I adding a raw value (oklch, px…)? 🛑 Stop → use a token instead
Am I writing transition: without @include? 🛑 Stop → @include transition()
Am I using position: absolute without @include? 🛑 Stop → @include absolute()
Am I using !important? 🛑 Stop → check the @layer order instead

Utilities

@layer syx.utilities — highest specificity. Always wins over atoms and molecules.

Typography Scale — .syx-type-*

.syx-type-display-1Display Hero
.syx-type-h1Heading One
.syx-type-h2Heading Two
.syx-type-h3Heading Three
.syx-type-body-largeBody large text.
.syx-type-bodyBody standard text.
.syx-type-body-smallBody small.
.syx-type-captionCaption — metadata.
.syx-type-labelLabel text.
.syx-type-overlineOverline — Section category

Text utilities

syx-text-center
syx-text-uppercase
syx-font-bold
syx-font-medium
syx-text-underline
syx-text-strikethrough
syx-max-w-50ch

Text Colors — .syx-text-*

.syx-text-primary .syx-text-secondary .syx-text-gray .syx-text-muted .syx-text-error .syx-text-success .syx-text-warning .syx-text-white .syx-text-inverse

Social brand

.syx-text-facebook .syx-text-twitter .syx-text-instagram .syx-text-whatsapp

Backgrounds — .syx-bg-* · .syx-bg-color-*

Semantic (_text.scss)

bg-white
bg-gray-50
bg-gray-100
bg-primary
bg-primary-light
bg-dark
bg-error
bg-success
bg-warning
bg-info

Theme-aware helpers (_backgrounds.scss)

.syx-bg-color-primary
.syx-bg-color-secondary
.syx-bg-color-black
Facebook
Twitter
Instagram
WhatsApp

Spacing — .syx-m/p-*

Margin bottom scale (0–5)

mb-0
mb-1
mb-2
mb-3
mb-4
mb-5

Padding scale (1–5) · shorthands

p-1
p-2
p-3
p-4
p-5
px-3 py-1
mx-auto

Display · Flex · Gap · Position

.syx-d-flex + gaps + wrap

Pill

.syx-justify-between + .syx-items-center

Section title

.syx-d-grid + .syx-gap-3

Col 1
Col 2
Col 3
Col 4

Media — img-fluid · embed · object-fit

.syx-img-fluid

Responsive demo

.syx-embed--16by9

Object-fit · Background-size classes

.syx-obj-cover .syx-obj-contain .syx-obj-fill .syx-bg-cover .syx-bg-contain

Accessibility — .syx-sr-only · skip-link · motion-safe

Skip Link — focus to reveal (Tab key)

.syx-sr-only — Visually hidden but accessible to screen readers.Hidden text for assistive technology.
.syx-sr-only-focusable — Becomes visible on keyboard focus.
.syx-motion-safe — Removes animations for prefers-reduced-motion.

Responsive visibility

.syx-d-sm-only .syx-d-sm-up .syx-d-md-up .syx-d-lg-up

Font Sizes — .syx-font-size-*

Responsive scale. Values map to --font-size-1…5 tokens. Sizes 2–5 scale up at $breakpoint-md.

.syx-font-size-1Font size 1
.syx-font-size-2Font size 2
.syx-font-size-3Font size 3
.syx-font-size-4Font size 4
.syx-font-size-5Font size 5

Dimensions — .syx-size-*

Square size tokens mapping to --primitive-size-1…5. For icons, avatars, thumbnails.

size-1
size-2
size-3
size-4
size-5

Spacer @deprecated

Legacy spacing classes kept for backward compatibility. Prefer syx-mt-* / syx-pt-* from the spacing utility.

.syx-spacer-gap-t-1 .syx-spacer-inner-t-1

Layout Grid

@layer syx.base — structural foundation. 12-column responsive grid.

layout-grid — xs / sm / md breakpoints

Column spans at xs

col-xs-12
col-xs-6
col-xs-6
xs-4
xs-4
xs-4

Responsive — xs-12 → sm-6 → md-4 (resize to see)

A
B
C

Modifiers: --no-gap · __nested · --is-edge2edge

--no-gap · xs-8
xs-4

Atoms

@layer syx.atoms — smallest reusable components.

Breadcrumb

Buttons — .atom-btn

Outline (no --filled) · default size

Filled (--filled) · default size

Sizes — --size-sm · --size-md · --size-lg (outline)

Sizes — filled

Circle — --circle with atom-icon · sm · md · lg

atom-btn--has-icon — icon size + gap auto-controlled by --size-*

States — normal · hover (hover me) · focus-visible (Tab) · disabled

Pills · Labels

Neutral Primary Secondary Success Warning Danger Dark

Icons — .atom-icon

Legacy system — --ui-* / --arrow-* / --rrss-*

Lucide — Navigation --lc-*

Lucide — Actions

Lucide — Status · User

Sizes — --sm · --md · --lg · --xl

Color modifiers — --color-primary · --color-state-ok · --color-state-ko · --color-state-warning

Title · Txt

H1 Title

H2 Title

H3 Title

H4 Title

H5 Title

H6 Title

Body paragraph with atom-txt--primary. Includes semantic bottom margin for vertical rhythm.

Second paragraph to demonstrate consistent spacing between text blocks.

Form — label · input · input-wrapper

Check · Switch

Demo Checkboxes
Demo Switches

Lists

  • First item
  • Second item
    • Nested A
    • Nested B
  • Third item
  1. First ordered
  2. Second ordered
  3. Third ordered

Table

Component Layer Status
atom-btn syx.atoms Stable
mol-card syx.molecules Stable
syx-d-flex syx.utilities Always wins

Pagination

Code — syntax highlight

.atom-code — block

.atom-btn {
  background-color: var(--component-btn-primary-bg);
  color: var(--component-btn-primary-color);
  /* token-driven — no hardcoded values */
}

.atom-code--inline

Use @layer syx.utilities to ensure utilities always win.

Radio

Demo Radio Group

Molecules

@layer syx.molecules — compositions of atoms.

Form Field — states

Validation error.
Looks good!
Consider improving.

Btn Group · Label Group

mol-btn-group

mol-label-group

SYX Design System v4.2.0 Beta features Production ready

Form Field Set — group wrapper

Default (vertical stack)

--inline (horizontal wrap with flex: 1 1 auto)

Organisms

@layer syx.organisms — compositions of molecules + atoms forming page-level sections.

Site Header — brand + nav + actions

Live example — the header at the top of this page is this organism

Brand Name

Composes: atom-icon · atom-btn · mol-btn-group. Add more organisms to scss/organisms/ and forward from organisms/index.scss.