Documentation
Everything you need to build with EXSA. Start with the 5-minute quick start, then dive into the concepts that make this framework different.
Getting Started
EXSA has zero dependencies. No npm, no webpack, no build step. It works with any server that serves static files — Apache, nginx, PHP, Python, Node, Caddy, or just opening .html files directly.
1. Download or clone
# Option A: Copy the dist/ folder into your project
your-project/
+-- dist/exsa.css ? core: tokens, reset, utilities, elements
+-- dist/exsa.fluid.css ? optional fluid tokens + density profiles
+-- dist/components/ ? 68 components (+ icons.css)
+-- dist/js/ ? 37 interactive behaviors + exsa-core.js
+-- dist/themes/ ? 20 themes + 1 blank custom starter
+-- dist/layouts/ ? general, dashboard, store
+-- dist/templates/ ? fullpage + onepage starter kits
# Option B: Git clone
git clone https://github.com/Saif-Almarri/exsa.git
# Then copy the dist/ folder into your project
2. Link the core
<link rel="stylesheet" href="dist/exsa.css">
<link rel="stylesheet" href="dist/themes/breeze.css">
exsa.css) is 32.4 KB (7.6 KB gzipped) and includes tokens, reset, utilities, classless element styles, and a built-in default theme. A theme file is optional — it overrides ~20 color tokens when you want your own palette. You're ready to build.Your First Page
Create an index.html with body class="exsa" to enable classless element styling — HTML elements automatically get typography, spacing, and link styling without writing a single CSS rule.
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="dist/exsa.css">
<link rel="stylesheet" href="dist/themes/breeze.css">
</head>
<body class="exsa">
<header>
<h1>My Site</h1>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
</ul>
</nav>
</header>
<main>
<h2>Welcome</h2>
<p>This paragraph gets automatic margin and typography.</p>
<p><code>Inline code</code> gets accent background styling.</p>
<section>
<aside>This becomes a card — border, shadow, responsive padding.</aside>
<aside>A second card. The section lays its direct children out in a row.</aside>
</section>
</main>
<footer>
<p>© 2026</p>
</footer>
</body>
</html>
The <header> is centered and padded. The <nav> is a flex row. <aside> elements inside <section> become cards. A classless <section> lays its direct children out in a flex row — that's why the two cards sit side by side, and why headings and paragraphs belong in <main> (or an <article>), where they stack in normal block flow. A plain <table> gets borders and header styling. A plain <button> gets padding, border, and hover feedback. All of this happens without a single class — it's the classless engine working. (Want a normal block-flow <section>? Add any class to it — see Guarded Styles.)
Adding Components
Three ways to add components:
- Ready-made bundles — link
exsa.bundle.css+exsa.jsand every component works. Fastest path. - Use the Bundle Generator — select only the components you use, pick a theme, and download minified CSS + JS with exactly your selection baked in.
- Download individual files — each component is a single CSS file in
dist/components/with its behavior indist/js/. Link only what you need.
Method 1: Ready-made bundles
<!-- One CSS file: EXSA core + all 68 components + icons + Breeze theme -->
<link rel="stylesheet" href="dist/exsa.bundle.css">
<!-- One JS file: exsa-core + all 37 behaviors -->
<script src="dist/exsa.js"></script>
Two tags and everything works — no per-component links, no per-behavior scripts. The bundle ships with the Breeze theme baked in and includes the icons library. Want a different theme? Link dist/themes/<name>.css after the bundle (the theme layer wins), or generate a custom bundle (Method 2). Not included: exsa.fluid.css, dist/layouts/, and dist/templates/ — link those separately when you use them.
Method 2: Bundle Generator
Go to generator.php, check the components you want, pick a theme (or "none" to bring your own), and download the CSS bundle + JS bundle — one click each. Two files, zero unused styles.
<!-- Generator output: EXSA core + selected components + theme -->
<link rel="stylesheet" href="dist/exsa.bundle.css">
<script src="dist/exsa.js"></script>
Method 3: Individual files
<!-- Add these after exsa.css -->
<link rel="stylesheet" href="dist/components/buttons.css">
<link rel="stylesheet" href="dist/components/card.css">
<link rel="stylesheet" href="dist/components/modal.css">
<link rel="stylesheet" href="dist/components/tabs.css">
<!-- For interactive components, link each behavior you use -->
<script src="dist/js/exsa-core.js"></script>
<script src="dist/js/modal.js"></script>
<script src="dist/js/tabs.js"></script>
Then use them in your HTML:
<button class="btn btn--primary">Click Me</button>
<div class="card">
<div class="card__body">
<h3 class="card__title">Card Title</h3>
<p class="card__text">Card description text.</p>
</div>
</div>
<div class="modal" id="my-modal">...</div>
exsa.bundle.css with everything you picked — no unused styles.The 9-Layer Cascade
EXSA uses CSS @layer to enforce an explicit cascade order. Each layer has one job. Layers lower in the list override layers higher — and themes sit directly above tokens, so theme overrides always win.
@layer exsa.tokens, exsa.themes, exsa.fluid, exsa.reset, exsa.utilities, exsa.elements, exsa.components, exsa.layouts, exsa.overrides;
@layer, specificity determines which rule wins — a class from a component could accidentally override a token. With layers, the cascade order is explicit and predictable regardless of specificity.Layer 1: Tokens
82 design tokens defined on :root (64 documented in tokens.json). Colors, spacing, typography, breakpoints, z-index scale, behavioral factors (--space-factor, --radius-factor, --font-factor, --motion-factor), shadows, border radius, hover brightness. Everything else references these.
Override any token in your own stylesheet or swap the theme file — all components update automatically.
Layer 2: Themes
One CSS file per theme — ~20 color-token overrides. Swap one <link> href and the whole site recolors. Themes sit above tokens in the cascade, so their overrides always win.
Layer 3: Fluid
The optional dist/exsa.fluid.css — clamp()-scaled spacing, type, and radius plus data-profile density factors. One file, no breakpoints. The prebuilt exsa.bundle.css does not include it — link it separately when you want fluid scaling.
Layer 4: Reset
Box-sizing, focus rings, scrollbar styling, RTL support, reduced motion, forced-colors mode, skip links, body defaults. Applies globally — no opt-in needed.
Layer 5: Utilities
89 utility classes (plus responsive variants) for flex, grid, gap, containers, positioning, text alignment, overflow, and responsive breakpoints. Always on. No prefix.
Layer 6: Elements
Classless HTML styling — activated by body class="exsa". Styles <p>, <a>, <code>, <pre>, <nav>, <section>, <aside>, <table>, <form>, <button>, <blockquote>, <dialog>, and more. Uses :where() for zero specificity.
Layer 7: Components
68 self-contained components — each is one CSS file. Link only what you need. BEM naming prevents collisions — no build tool required.
Layer 8: Layouts
Drop-in page shells (general, dashboard, store) — topbar, sidebar, and content zones wired to the layout tokens.
Layer 9: Overrides
The u-* escape hatch — single-property utilities that always beat components and layouts. Your own unlayered CSS still beats them.
Tokens — The Source of Truth
Every visual decision in EXSA flows from 82 design tokens in :root (64 documented in tokens.json). Change a token — every component, every element, every utility that references it updates instantly.
/* Key tokens you'll override most often */
--color-bg: #fff; /* page background */
--color-text: #000; /* body text */
--color-link: #118bee; /* links, primary actions, focus ring */
--color-secondary: #920de9; /* accent, visited links */
--color-bg-secondary: #e9e9e9; /* cards, stripes, borders */
--border-radius: 5px; /* all rounded corners */
--box-shadow: 2px 2px 10px; /* shadow offset + blur */
--font-family: ...; /* system font stack */
/* Spacing scale — 7 steps */
--gap-xs: 0.25rem; --gap-sm: 0.5rem; --gap-md: 0.75rem;
--gap: 1rem; --gap-lg: 1.5rem; --gap-xl: 2rem;
--gap-2xl: 3rem;
/* Semantic colors */
--color-success: #16a34a; /* green */
--color-danger: #dc2626; /* red */
--color-warning: #d97706; /* amber */
Full token reference: Cheatsheet → Tokens
tokens.json exports the token catalog in structured JSON — 64 core tokens (of the 82 in exsa.css), the z-index scale, and component/layout tokens — import into Figma (via Tokens Studio), JavaScript, or Tailwind config. Light and dark values included for every color token.Classless Elements
Add class="exsa" to <body> and semantic HTML elements get automatic styling:
<mark>— highlighted text with padding<sup>— pill badge with secondary background<details>— expandable disclosure widget with bold summary<p>— 0.75rem vertical margin<a>— bold, underlined, link-colored, brightness on hover/active<code>— inline code with accent background, border-radius, padding<pre>— block code with max-width, padding, scroll overflow<hr>— 1px divider, 3rem margin<nav>— flex row, space-between, bold<section>— flex-wrap row container: direct children (cards, figures, paragraphs) lay out side by side<aside>— card with border, shadow, hover lift, container-responsive padding<table>— bordered, header background, cell padding<form>— card-style wrapper with styled inputs, labels, selects, textareas<button>— neutral bordered button with hover and click feedback<blockquote>— centered, large text with optional attribution footer<dialog>— modal with backdrop, animation, and responsive width
All classless styles use :where() — specificity of zero. A single class from your own CSS always wins.
Guarded Styles
EXSA uses :not([class]) on structural element rules — <section>, <header>, <main>, <footer>, <nav>, <table>, <form>, <button>, <blockquote>, <dialog>. If you add any class to these elements, EXSA's styling steps aside completely.
<!-- EXSA styles this: flex-wrap, centered cards -->
<section>
<aside>Card 1</aside>
<aside>Card 2</aside>
</section>
<!-- EXSA styles this: bordered, header bg, cell padding -->
<table>...</table>
<!-- EXSA stays out — you're in control -->
<section class="my-custom-layout">
<table class="data-grid">...</table>
</section>
<table class="data-grid"> — no borders, no header bg. <button class="special"> — no padding, no border, no hover. You get a clean slate. Then add the component file (table.css, buttons.css) and use BEM classes for the upgraded experience.<section> elements use container-type: inline-size for responsive card layouts. This establishes a new containing block — any position: absolute or position: fixed children position relative to the section, not the viewport. If this causes unexpected positioning, simply add any class to the <section> — EXSA will step aside and remove the container query.How Themes Work
A theme is a CSS file that overrides ~20 color tokens on :root. Themes live in @layer exsa.themes — directly above tokens in the cascade — so their overrides always beat the default token values.
/* themes/night.css — a complete theme in ~20 lines */
@layer exsa.themes {
:root {
color-scheme: light dark;
--color-bg: light-dark(#f8fafc, #0f1117);
--color-bg-secondary: light-dark(#e2e8f0, #1c1f2e);
--color-text: light-dark(#0f172a, #e2e8f0);
--color-link: light-dark(#2563eb, #60a5fa);
--color-secondary: light-dark(#7c3aed, #c084fc);
--color-accent: light-dark(#2563eb10, #60a5fa12);
--color-secondary-accent: light-dark(#7c3aed0d, #c084fc12);
--color-success: light-dark(#16a34a, #22c55e);
--color-danger: light-dark(#dc2626, #ef4444);
--color-warning: light-dark(#d97706, #f59e0b);
--color-scrollbar: light-dark(#94a3b8, #334155);
--border-radius: 6px;
}
}
Swap themes by changing one <link> href. All 68 components, all element styles, all utilities — everything recolors instantly. No rebuild.
Creating a Custom Theme
Copy any existing theme file (start with breeze.css for light, night.css for dark) and override these tokens:
- Background pair:
--color-bg+--color-bg-secondary - Text pair:
--color-text+--color-text-secondary - Brand colors:
--color-link+--color-secondary - Accent tints:
--color-accent+--color-secondary-accent(use 8-digit hex with alpha) - Semantic colors:
--color-success,--color-danger,--color-warning(and their-hovervariants) - Chrome:
--color-scrollbar,--color-shadow
#118bee15 means #118bee at ~8% opacity. The last two digits are hex alpha — 00 (transparent) to FF (opaque). Use this for subtle tint backgrounds.Dark Mode & System Preference
EXSA supports three approaches to dark mode:
1. System preference (built-in)
The core tokens use CSS light-dark() with color-scheme: light dark — dark values ship in the same definition. Load no theme file and the framework follows OS preference automatically.
2. Forced mode
Every theme ships both light and dark values via light-dark(). Force either mode regardless of OS preference with one attribute:
<html data-theme-mode="dark">
<html data-theme-mode="light">
3. Runtime switching (JavaScript)
// Switch theme programmatically
document.getElementById('theme-stylesheet').href = 'dist/themes/night.css';
Layout Utilities
89 base utility classes (plus responsive variants) in the utilities layer. Always on, no prefix, no breakpoint memorization. Full reference: Cheatsheet → Layout.
Containers
.container /* max-width: 1080px, centered, padded */
.container-sm /* max-width: 800px */
.container-full /* width: 100% */
Flex
.flex .flex-col .flex-wrap .flex-1
.justify-center .justify-between .items-center
.gap-sm .gap .gap-lg .gap-xl
Grid
.grid .grid-cols-3 .grid-auto-fit
Position & Sizing
.relative .absolute .fixed .sticky .inset-0
.w-full .h-full .overflow-hidden .overflow-auto
.text-center .text-left .text-right
Typography
20 utility classes for font sizing, weight, family, and transforms — all driven by --font-* tokens. Swap a token, every element using these classes updates instantly.
Override utilities (u-*)
Unprefixed utilities are structural and lose to components. When you deliberately need to adjust a component — card u-text-center, btn u-w-full — the u-* classes in the top exsa.overrides layer always win over components and layouts. Your own unlayered CSS still beats them.
.u-text-center .u-text-end .u-flex .u-none
.u-w-full .u-m-0 .u-p-0 .u-mx-auto
.u-gap-0 .u-gap-sm .u-gap .u-gap-lg
.u-radius-0 .u-radius-full
| Group | Classes | Token |
|---|---|---|
| Font sizes | .text-xs .text-sm .text-base .text-md .text-lg .text-xl .text-2xl | --font-size-* (11px–29px) |
| Font weights | .fw-normal .fw-bold .fw-heavy | --font-weight-* (400/700/800) |
| Font families | .font-sans .font-mono .font-heading | --font-family / --font-family-heading |
| Transforms | .italic .uppercase .capitalize | — |
| Utilities | .text-muted .text-start .text-center .text-end | --color-text-secondary |
<!-- Typography utilities in action -->
<h1 class="text-2xl fw-heavy font-heading">Main Heading</h1>
<p class="text-base text-muted">Body copy with muted color</p>
<span class="text-sm fw-bold uppercase">small bold caps</span>
Page Layouts
EXSA ships with 3 composable layouts + 2 starter templates — drop-in CSS files that turn semantic HTML zones into complete page shells. Link the one you need, add its classes to <body>, and structure your HTML with the documented zones.
The Layouts & Templates
| Layout | File | Use For |
|---|---|---|
| General | dist/layouts/general.css | Documentation sites, reference pages — topbar + sidebar + content + footer |
| Blog | dist/layouts/general.css | Articles, prose — blog mode (blog--prose, blog--has-toc) merged into the general layout |
| Dashboard | dist/layouts/dashboard.css | Admin panels, web apps — fixed sidebar, scrollable content |
| Store | dist/layouts/store.css | Ecommerce — announcement bar, cart topbar, filter sidebar, product grid |
| Fullpage | dist/templates/fullpage/ | Starter template: single-viewport landing — media/panned backdrop, centered brand, social rings, pure-CSS panels, copyright |
| Onepage | dist/templates/onepage/ | Starter template: multi-section marketing page — photo hero, nav dropdowns, banner, carousel, features grid, dark footer |
How They Work
All five layouts follow the same pattern:
- Link the layout CSS after
exsa.cssand your theme. - Add the layout's body classes to
<body>. - Structure your HTML with the documented zone elements.
Layouts use the topbar component's --topbar-height variable — swap topbar--sm for topbar--xl and body padding updates automatically. Fixed zones across layouts and components share one token-driven z-index scale (--z-topbar, --z-modal, --z-layout-aside…), so you can retune stacking from a single place in your theme.
Responsive Helpers
Five breakpoints. Classes activate at or above the specified width (except sm: which applies below):
| Breakpoint | Width | Target | Examples |
|---|---|---|---|
sm: | = 575px | Phones | .sm\:flex-col .sm\:grid-cols-1 |
md: | = 768px | Tablets | .md\:col-3 .md\:grid-cols-3 |
lg: | = 1024px | Laptops | .lg\:col-4 .lg\:grid-cols-4 |
xl: | = 1280px | Desktops | .xl\:col-5 .xl\:grid-cols-5 |
xxl: | = 1440px | Large Desktops | .xxl\:col-5 .xxl\:grid-cols-5 |
Breakpoint Tokens & JS
All 5 breakpoints are CSS tokens (--bp-sm through --bp-xxl) on :root. They can't be used directly in CSS @media queries today — that needs @custom-media (draft spec). But they power EXSA.bp for JS-driven responsive logic, keeping breakpoints as a single source of truth:
// Tablet and up?
if (EXSA.bp.up('md').matches) {
// 768px+
}
// Mobile only — reactive
EXSA.bp.down('sm').addEventListener('change', function(e) {
if (e.matches) { /* <= 575px */ }
});
// Raw pixel value
var w = EXSA.bp.val('lg'); // "1024px"
--bp-md in your theme and EXSA.bp.up('md') reads the new value automatically. (The responsive utility classes inside exsa.css use fixed values — CSS can't read custom properties in media queries without @custom-media, a draft spec.) No magic numbers. One source of truth for JS.Fluid Tokens & Profiles
EXSA offers an optional, per-project fluid-first approach to responsive design — ideal for content sites and large screens. Instead of breakpoints and responsive classes, tokens themselves scale smoothly with viewport width using clamp(). Link one file and every component becomes fluid and the data-profile density attributes activate. Without this file, tokens stay static and data-profile has no effect.
Setup
<!-- Core -->
<link rel="stylesheet" href="dist/exsa.css">
<link rel="stylesheet" href="dist/themes/breeze.css">
<!-- Fluid tokens + profiles — optional, link after core -->
<link rel="stylesheet" href="dist/exsa.fluid.css">
What Gets Fluid
| Token Group | Example | Effect |
|---|---|---|
| Spacing (7 tokens) | --gap: clamp(0.75rem, 0.6rem + 0.5vw, 1.25rem) | Gaps, padding, margins scale with viewport |
| Typography (7 tokens) | --font-size-base: clamp(0.82rem, 0.8rem + 0.3vw, 1.05rem) | Font sizes scale smoothly — no breakpoint jumps |
| Shape | --border-radius: clamp(4px, 0.3vw, 10px) | Corners round proportionally on larger screens |
| Layout | --width-content: clamp(320px, 90%, 1080px) | Containers fill available space, capped at max-width |
Behavioral Profiles
Add data-profile to <html> to change the density of every component via factor tokens — no class changes, no rebuild. Requires exsa.fluid.css (linked above):
| Profile | Attribute | Space | Radius | Type | Motion |
|---|---|---|---|---|---|
| Compact | data-profile="compact" | 0.8× | 0.85× | 0.95× | 0.6× |
| Comfortable | (default — no attribute) | 1× | 1× | 1× | 1× |
| Spacious | data-profile="spacious" | 1.3× | 1.2× | 1.1× | 1.4× |
<!-- Switch profiles with one attribute change -->
<html data-profile="compact"> <!-- denser dashboards, data-heavy apps -->
<html data-profile="spacious"> <!-- landing pages, marketing, kiosks -->
.md\:grid-cols-3) or auto-fit/minmax() grids.Component Model
Every EXSA component follows the same pattern:
- One CSS file per component —
dist/components/buttons.css,dist/components/modal.css - BEM naming —
.card,.card__title,.card--hoverable - Structured markup — each component expects a fixed DOM skeleton (e.g. progress bars:
.progress-item > .progress-item__track > .progress-item__fill; tabs:.tabsfollowed by a sibling holding.tabs__panelelements). Missing a required wrapper makes children collapse silently (a 100%-height fill inside a heightless parent is 0px). Copy the exact structure from the showcase snippets. - Contract linter (development) — link
dist/exsa.debug.cssand adddata-debugto<html>: any component root missing a required part gets a red dashed outline plus a label naming the missing part. Loaddist/exsa.debug.jsafter it for amber class-name spellchecking with "did you mean?" suggestions. Never ship either to production. - Collision-free — BEM naming prevents collisions; no
:where()needed - Token-driven — colors, spacing, shadows reference CSS custom properties
- Modifier classes — variants use
--suffix:.btn--primary,.alert--success,.badge--dot
/* What a component looks like internally */
.card {
background: color-mix(in srgb, var(--color-bg) 97%, var(--color-text));
border: 1px solid var(--color-bg-secondary);
border-radius: var(--border-radius);
}
.card--hoverable:hover {
box-shadow: var(--box-shadow);
transform: translateY(-2px);
}
BEM Naming Convention
EXSA uses BEM (Block, Element, Modifier) for all component classes. It's the simplest way to guarantee zero naming collisions across 68 components — no build tool required.
The three rules
| Part | Pattern | Example | Means |
|---|---|---|---|
| Block | .block | .card | The component root |
| Element | .block__element | .card__title | A child of the block |
| Modifier | .block--modifier | .card--hoverable | A variant or state |
Why not utility classes? Why not CSS Modules?
| Approach | Needs build step? | Naming collisions? | Token-friendly? |
|---|---|---|---|
| BEM (EXSA) | No | No — unique prefixes | Yes — all values are var(--token) |
| Utility (Tailwind) | Required | No — atomic classes | No — hardcoded values |
| CSS Modules | Required | No — auto-scoped | Yes — can use var() |
| Scoped (Vue/Svelte) | Framework-dependent | No — auto-scoped | Yes — can use var() |
Abbreviated modifiers
For common variants, EXSA uses short, memorable modifier names:
| Modifier | Used on | Means |
|---|---|---|
--sm / --lg / --xl | Most components | Size variants |
--primary / --success / --danger / --warning | Buttons, badges, alerts | Semantic color variants |
--outline / --ghost / --soft / --surface | Buttons, dropdowns | Style variants |
--vertical / --horizontal | Stepper, data-list, separator | Orientation |
--active / --disabled / --loading | Stateful components | Current state |
var(--token) for all values. Your component will feel native to EXSA and theme-switch automatically.JavaScript Behaviors
Each interactive component has one behavior file in dist/js/ — modal.js, tabs.js, dropdown.js… 37 behaviors in total. Class-driven — no hardcoded IDs, no configuration objects. Each file scans the DOM for its component classes and initializes them automatically. Load dist/js/exsa-core.js first (focus-trap utilities), then only the behaviors you use.
Components that need JS
| Component | What JS does | Lines |
|---|---|---|
| Dropdown | Toggle open/close, outside click dismiss | ~37 |
| Modal | Open/close, backdrop, Escape, focus trap | ~48 |
| Slideshow | Auto-advance, arrows, dots, pause on hover | ~39 |
| Tabs | Panel toggling, active state | ~41 |
| Toast | Create, auto-dismiss, stack | ~31 |
| Lightbox | Open/close, prev/next, keyboard nav | ~63 |
| Context Menu | Right-click trigger, position clamping | ~61 |
| Popover | Click trigger, outside/Escape close | ~34 |
| Topbar | Scroll shadow, mobile toggle, dropdowns | ~71 |
| Music Player | Play/pause toggle, time tracking | ~34 |
| And 27 more | Cookie bar, rating, color picker, advanced color picker, date picker, password toggle, range slider, back-to-top, video gallery, resizer, sidebar, accordion, toggle, drawer, carousel, code block, RTL toggle, scroll spy, theme switcher, calendar, chart, command palette, kanban, table sort/filter/select, tags input, transfer list, upload dropzone | ~20-70 each |
Tree-Shaking & Generator
During development, link individual component CSS files. For production, use the Generator to create a single bundle:
- Pick your theme
- Select the components you use
- Download the CSS bundle + the JS bundle (one click each)
- Replace all individual
<link>tags with one
<!-- Development: individual files -->
<link href="dist/exsa.css">
<link href="dist/themes/breeze.css">
<link href="dist/components/card.css">
<link href="dist/components/modal.css">
<!-- Production: one file -->
<link href="dist/exsa.bundle.css">
Icons Library
EXSA includes 112 SVG icons — 103 Feather Icons (stroke-based, MIT) + 9 Simple Icons brand logos (CC0). All rendered via CSS masks with currentColor — theme colors apply automatically. Icons inherit the text color of their parent.
Usage
<!-- Basic usage with the .ic base + .ic-{name} -->
<span class="ic ic-search"></span>
<span class="ic ic-heart"></span>
<!-- In buttons -->
<button class="btn btn--primary">
<span class="ic ic-download"></span> Download
</button>
<!-- With custom size -->
<span class="ic ic-settings" style="width:20px;height:20px"></span>
Setup
Add one stylesheet — it defines the .ic base mask system and maps all 112 icons to their SVG files (the paths are relative, so it works from any location):
<!-- Icon system — one file -->
<link rel="stylesheet" href="dist/components/icons.css">
Migrating from Bootstrap
| Bootstrap | EXSA | Notes |
|---|---|---|
.container | .container | Same API |
.row / .col-md-6 | .flex + .col-2 or .grid + .grid-cols-2 | No row wrapper needed |
.btn-primary | .btn .btn--primary | BEM naming, more variants |
.alert-info | .alert .alert--info | Also has soft/outline/surface styles |
.badge | .badge | Same API, more colors |
.navbar | .topbar | Fixed top, scroll shadow, mobile hamburger |
.modal | .modal | Nested dialog with header/body/footer |
.spinner-border | .spinner | 3 sizes, pure CSS |
| ~230 KB | 32.4 KB core (add components — la carte) | ~7× smaller core |
Migrating from Tailwind
EXSA takes the opposite approach: semantic components instead of atomic utilities. Here's how the mental model translates:
| Tailwind | EXSA | Philosophy |
|---|---|---|
flex justify-between items-center gap-4 | flex justify-between items-center gap | Layout utilities are similar — EXSA uses tokens for spacing |
bg-white rounded-lg shadow-md p-6 border | card | One component class replaces 5+ utilities |
text-lg font-semibold text-gray-900 | card__title | Semantic class carries meaning |
| Edit every class to change theme | Swap one theme file | Tokens vs hardcoded values |
.flex, .grid, .gap, .text-center give you utility-class convenience. But for recurring UI patterns, EXSA's components eliminate class repetition.