Compiler Engine

FrontAlign ships with a built-in JIT Compiler Engine. It analyzes your project files, extracts active CSS utilities, processes your frontalign.config.js configuration, and outputs an optimized frontalign.build.css file containing only what your project actually needs.

Beyond utility extraction, the compiler also:

  • Generates theme tokens from frontalign.config.js
  • Compiles dark mode variables
  • Compiles responsive breakpoints into media queries
  • Creates font utility classes and custom classes
  • Purges unused @keyframes blocks
  • Removes unused CSS variables while preserving their dependency chains

The result is a lightweight, production-ready stylesheet that contains only the styles, variables, and generated assets your project actually uses. FrontAlign does not simply scan class names and copy matching CSS rules. It builds a dependency-aware CSS output. The compiler reads FrontAlign's core CSS, extends :root with your theme (including extend, dark, dark.extend, and breakpoints), generates fonts and classes, scans your source files, resolves used classes and variables, and finally writes everything out organized into cascade layers — theme, base, components, utilities, custom — in that order.

Production Build

npx frontalign build 

Build mode compiles the final CSS and applies production optimization. The output is written to: public/frontalign.build.css

Import it in your application:

import '../public/frontalign.build.css' 

Or include it in HTML:

<link rel="stylesheet" href="/frontalign.build.css" />

Development Watch Mode

npx frontalign dev 

Watch mode monitors your config file, FrontAlign's main CSS file, and your configured scan targets. When any file changes, frontalign.build.css is rebuilt automatically.

When frontalign.css itself changes, the compiler cache is automatically invalidated and a full rebuild is triggered.

frontalign.config.js Setup

The compiler requires a frontalign.config.js file in your project root. Here is a complete example showing all available options:

export default {
  jit: {
    scan: ['./app', './components', './pages'],
    safelist: ['button', 'is-primary', 'is-active'],
    extensions: ['mdx'],
    debug: false,
    concurrency: 50
  },

theme: {
primary: '#2563eb',
body: '#ffffff',
bodyText: '#111827',
font: 'Inter, sans-serif',

    extend: {
      'brand-surface': '#f8fafc',
      'brand-border': '#e2e8f0'
    },

    dark: {
      body: '#0f172a',
      bodyText: '#e5e7eb',

      extend: {
        'brand-surface': '#111827',
        'brand-border': '#334155'
      }
    },

    breakpoints: {
      sm: '640px',
      md: '864px',
      lg: '1120px',
      xl: '1408px',
      '2xl': '1792px'
    }

},

fonts: [
{
family: 'Inter',
weights: '400;500;600;700',
alias: 'inter',
category: 'sans-serif'
}
],

classes: {
'hero-card': {
'background': 'var(--brand-surface)',
'border': '1px solid var(--brand-border)',
'border-radius': '1rem',
'padding': '2rem',

      dark: {
        'background': 'var(--brand-surface)',
        'border-color': 'var(--brand-border)'
      }
    },

    'text-gradient': 'background: linear-gradient(90deg, #2563eb, #60a5fa); -webkit-background-clip: text; color: transparent;'

}
}

dark.extend works the same way as the root-level theme.extend: it lets you define dark-mode-only values for custom tokens. Any key placed directly under dark that isn't one of FrontAlign's recognized theme keys (body, bodyText, primary, primaryContrast, font, fontMono, spaceXsspace2xl) is silently ignored unless it's nested under dark.extend.

CSS Output

The config above compiles into the following frontalign.build.css output. FrontAlign organizes everything into five CSS cascade layers, always in this order — theme, base, components, utilities, custom — so specificity never has to be fought with source order:

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

@layer theme, base, components, utilities, custom;

@layer theme {
  :root {
    --body: #ffffff;
    --body-text: #111827;
    --primary: #2563eb;
    --font: Inter, sans-serif;
    --brand-surface: #f8fafc;
    --brand-border: #e2e8f0;
  }

  [fa-theme="dark"] {
    --body: #0f172a;
    --body-text: #e5e7eb;
    --brand-surface: #111827;
    --brand-border: #334155;
  }
}

@layer base {
  /* FrontAlign's base/reset styles */
}

@layer components {
  /* FrontAlign's own bundled component styles, filtered by the JIT scan */
}

@layer utilities {
  @media (min-width: 640px) {
    /* sm utilities */
  }

  @media (min-width: 864px) {
    /* md utilities */
  }

  /* ... active utility classes extracted by the JIT scan ... */
}

@layer custom {
  .font-inter {
    font-family: 'Inter', sans-serif;
  }

  .hero-card {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 1rem;
    padding: 2rem;
  }

  [fa-theme="dark"] .hero-card {
    background: var(--brand-surface);
    border-color: var(--brand-border);
  }

  .text-gradient {
    background: linear-gradient(90deg, #2563eb, #60a5fa);
    -webkit-background-clip: text;
    color: transparent;
  }
}
LayerComes from
themetheme — root tokens, extend, dark (including dark.extend), and breakpoints
baseFrontAlign's own base/reset styles
componentsFrontAlign's own bundled component styles, filtered by the JIT scan — not your classes config
utilitiesBreakpoint media queries and JIT-extracted utility classes from FrontAlign's bundled CSS
customfonts-generated .font-* classes, plus every classes entry — object form (e.g. hero-card) and raw-string form (e.g. text-gradient) alike

Because each layer is declared up front with @layer theme, base, components, utilities, custom;, a rule in custom always beats a rule of equal specificity in utilities, and so on — regardless of where either rule physically sits in the file.

All entries under classes — whether written as a declaration object or a raw CSS string, with or without a dark sub-key — compile into the custom layer. The components layer is reserved for FrontAlign's own bundled component styles; it does not contain anything from your config.

Scan Targets

The jit.scan option tells FrontAlign where to look for used classes.

export default {
  jit: {
    scan: ['./app', './components']
  }
}

When a folder path is provided, FrontAlign automatically scans all supported source files inside that folder. You can also pass direct file paths or glob patterns:

export default {
  jit: {
    scan: ['./app', './components/Button.tsx', './pages/**/*.jsx']
  }
}

Supported Source Files

By default, FrontAlign only scans .js, .jsx, .ts, and .tsx files. Any other extension — including .html and .php — must be explicitly added via jit.extensions (see Scan Performance & Extensions below) before FrontAlign will scan files with that extension.

ExtensionUse caseScanned by default?
.jsJavaScript filesYes
.jsxReact JSX filesYes
.tsTypeScript filesYes
.tsxReact TypeScript filesYes
.htmlStatic HTML filesNo — requires jit.extensions: ['html']
.mdxMDX documentation and content filesNo — requires jit.extensions: ['mdx']

Class Extraction

FrontAlign detects classes from plain HTML, JSX, template strings, and common class composition helpers:

PatternExample
class<div class="card p-4">
className<div className="card p-4" />
className={...}

<div className={card $&#123;active ? 'is-active' : ''&#125;} />

clsx(), cn(), cx()

clsx('button', active && 'is-active')
cva()

cva('button', { variants: { intent: { primary: 'is-primary' } } })

tv(), variants()

tv({ base: 'card', variants: { size: { lg: 'p-6' } } })

Dynamic expressions inside template strings (${...}) are stripped during extraction, but the static class names surrounding them are collected.

Safelist

Use safelist when a class is generated dynamically and cannot be reliably detected from source code.

export default {
  jit: {
    scan: ['./app', './components'],
    safelist: [
      'button',
      'is-primary',
      'is-danger',
      'is-active'
    ]
  }
}

Safelisted classes are always included in the generated CSS output regardless of whether they appear in scanned files.

Scan Performance & Extensions

Beyond scan and safelist, jit accepts a few more options that control how scanning behaves:

OptionDefaultDescription
extensions[]Adds extra file extensions on top of the default js, jsx, ts, tsx. Required to scan .html, .mdx, or any other file type.
concurrency50Maximum number of files read, hashed, and scanned in parallel. Lower this if you hit file-descriptor limits on very large projects.
debugfalseWhen true, logs a warning to the console for any file the scanner could not resolve, stat, hash, or read, instead of silently skipping it.
export default {
  jit: {
    scan: ['./app', './components', './content'],
    extensions: ['html', 'mdx'],
    concurrency: 100,
    debug: true
  }
}

Theme Tokens

Define your core design tokens in frontalign.config.js:

export default {
  theme: {
    body: '#ffffff',
    bodyText: '#111827',
    primary: '#2563eb',
    font: 'Inter, sans-serif',
    fontMono: 'Fira Code, monospace',
    spaceXs: '0.25rem',
    spaceSm: '0.5rem',
    spaceMd: '1rem',
    spaceLg: '1.5rem',
    spaceXl: '2rem',
    space2xl: '3rem'
  }
}

These values are compiled into CSS variables:

Config keyGenerated CSS variable
body--body
bodyText--body-text
primary--primary
font--font
fontMono--font-monospace
spaceXs--space-xs
spaceSm--space-sm
spaceMd--space-md
spaceLg--space-lg
spaceXl--space-xl
space2xl--space-2xl

Only the keys listed above are recognized. Any other key placed directly under theme is ignored — use theme.extend for anything custom.

Extending Tokens

Use theme.extend to add custom CSS variables:

export default {
  theme: {
    extend: {
      'brand-surface': '#f8fafc',
      'brand-border': '#e2e8f0',
      '--brand-radius': '1rem'
    }
  }
}

FrontAlign normalizes custom token names into CSS variables automatically — a key without a -- prefix gets one added; a key that already has it is left untouched.

Dark Mode Tokens

theme.dark provides dark-mode values for any of the recognized theme keys (body, bodyText, primary, primaryContrast, font, fontMono, spaceXsspace2xl). For custom tokens added via theme.extend, use the matching theme.dark.extend to override them in dark mode:

export default {
  theme: {
    body: '#ffffff',
    bodyText: '#111827',

    extend: {
      'brand-surface': '#f8fafc',
      'brand-border': '#e2e8f0'
    },

    dark: {
      body: '#0f172a',
      bodyText: '#e5e7eb',

      extend: {
        'brand-surface': '#111827',
        'brand-border': '#334155'
      }
    }

}
}

This compiles into:

:root {
  --body: #ffffff;
  --body-text: #111827;
  --brand-surface: #f8fafc;
  --brand-border: #e2e8f0;
}

[fa-theme="dark"] {
  --body: #0f172a;
  --body-text: #e5e7eb;
  --brand-surface: #111827;
  --brand-border: #334155;
}

A key placed directly under theme.dark that isn't one of the recognized theme keys is silently dropped — it must go under theme.dark.extend instead. In the example above, writing 'brand-surface': '#111827' as a direct sibling of body inside dark (instead of inside dark.extend) would not produce any output for it.

Breakpoints

Responsive breakpoints are also part of theme, defined under theme.breakpoints, right alongside colors, fonts, and spacing. FrontAlign ships with the following defaults:

export const DEFAULT_BREAKPOINTS = {
  sm: '640px',
  md: '864px',
  lg: '1120px',
  xl: '1408px',
  '2xl': '1792px'
};

Override any tier by setting it under theme.breakpoints:

export default {
  theme: {
    primary: '#2563eb',

    breakpoints: {
      sm: '480px',
      md: '768px',
      lg: '1024px',
      xl: '1280px',
      '2xl': '1536px'
    }
  }
}

Any tier left out of theme.breakpoints falls back to DEFAULT_BREAKPOINTS — you don't have to redefine every key. The compiler turns each tier into a min-width media query, and only emits the ones actually used by scanned classes (md:hero-card, lg:hidden, and so on):

@media (min-width: 480px) {
  /* sm utilities */
}

@media (min-width: 768px) {
  /* md utilities */
}

@media (min-width: 1024px) {
  /* lg utilities */
}

Like every other key inside theme, changing breakpoints while npx frontalign dev is running triggers a rebuild of frontalign.build.css with the new media query values — no restart required.

breakpoints can also be set at the top level of the config (export default { breakpoints: {...} }) instead of under theme. If both are present, theme.breakpoints takes priority.

Custom Classes

The compiler generates custom classes defined in frontalign.config.js. All classes entries — object form or raw-string form — compile into the custom layer, regardless of how they're written:

export default {
  classes: {
    'hero-card': {
      'background': 'var(--brand-surface)',
      'border': '1px solid var(--brand-border)',
      'border-radius': '1rem',
      'padding': '2rem'
    }
  }
}

Generated CSS:

@layer custom {
  .hero-card {
    background: var(--brand-surface);
    border: 1px solid var(--brand-border);
    border-radius: 1rem;
    padding: 2rem;
  }
}

Class rules can also be defined as raw CSS declaration strings:

export default {
  classes: {
    'text-gradient': 'background: linear-gradient(90deg, #2563eb, #60a5fa); -webkit-background-clip: text; color: transparent;'
  }
}

Dark Mode Custom Classes

Custom classes can include dark mode overrides:

export default {
  classes: {
    'hero-card': {
      'background': '#ffffff',
      'color': '#111827',

      dark: {
        'background': '#111827',
        'color': '#e5e7eb'
      }
    }

}
}

Generated CSS:

@layer custom {
  .hero-card {
    background: #ffffff;
    color: #111827;
  }

  [fa-theme="dark"] .hero-card {
    background: #111827;
    color: #e5e7eb;
  }
}

Font Generation

FrontAlign generates Google Font imports and font utility classes from your config:

export default {
  fonts: [
    {
      family: 'Inter',
      weights: '400;500;600;700',
      alias: 'inter',
      category: 'sans-serif'
    }
  ]
}

Generated utility class — compiled into the custom layer, alongside your classes entries:

@layer custom {
  .font-inter {
    font-family: 'Inter', sans-serif;
  }
}

Usage:

<div class="font-inter">FrontAlign typography</div>

If alias is omitted, it is derived automatically from the family name — for example "DM Sans" becomes font-dm-sans. If category is omitted, it defaults to sans-serif.

CSS Variable Optimization

FrontAlign tracks CSS variables used inside scanned files, generated config CSS, base styles, and extracted utility CSS.

When JIT scanning is active, the compiler removes unused :root variables from the final output while keeping variables that are actually required.

It also resolves nested variable dependencies. For example, if your code uses:

color: var(--button-text); 

and --button-text references another variable:

--button-text: var(--primary); 

FrontAlign keeps both variables because the second is required by the first.

Component-Layer Purge Fallback

FrontAlign purges the components layer with one extra rule beyond exact matching: if a root class (e.g. card) is directly used, scanned, or safelisted, any component-layer selector containing a class that is a hyphenated sub-part of it (e.g. .card-header, .card-active) is kept automatically — even if that exact sub-class never appears literally in your source files. This exists because such classes are often only ever set dynamically via JS (classList, innerHTML, setAttribute) that no static scanner can see.

This fallback intentionally does not apply to the utilities layer: there, every class must match exactly, so using flex never silently keeps unrelated flex-1 or flex-wrap rules alive — utilities are atomic and independent, so the same fallback there would break utility purging almost entirely.

This only covers hyphenated class names on the same selector — a descendant selector like .card .icon still requires icon to be used or safelisted on its own. Classes appearing only inside :not(), :has(), :is(), or :where() are treated as conditions, not requirements, and never gate whether a rule is kept.

Keyframes Purging

FrontAlign also purges unused @keyframes blocks automatically. It scans every animation and animation-name declaration across the base, component, and utility layers, collects the animation names actually referenced, and keeps only the @keyframes definitions that match — dropping the rest from the final output. No extra configuration is required.

Cache System

FrontAlign uses a local cache to speed up repeated builds.

The cache stores per-file scan data including:

  • File modification time (mtime)
  • File hash
  • Extracted classes
  • Extracted CSS variables

The cache is stored at:

node_modules/.cache/frontalign/cache.json

When a file has not changed, FrontAlign reuses the cached extraction result instead of re-scanning it.

When frontalign.css changes during dev mode, the cache is automatically invalidated and a full rebuild runs.

Ignored Paths

FrontAlign ignores common project folders that should not be scanned:

PatternReason
/node_modules/Dependency files should not be scanned.
/.git/Git internals are not source files.

Build Pipeline

The full compiler pipeline runs in this order:

frontalign.config.js
   ↓
Load FrontAlign's bundled CSS (theme / base / components / utilities / keyframes inventory)
   ↓
Load cache & scan project files
   ↓
Extract classes and CSS variables per file
   ↓
Build theme AST (root & dark tokens, extend, dark.extend, fonts, classes → custom layer)
   ↓
Resolve breakpoints
   ↓
Filter active component & utility rules (JIT purge, incl. hyphen-prefix fallback for components)
   ↓
Filter active @keyframes by referenced animation names
   ↓
Resolve CSS variable dependency chains & filter unused root/dark variables
   ↓
Merge and organize into layers (PostCSS): theme → base → components → utilities → custom
   ↓
Apply breakpoint media queries
   ↓
Optimize production CSS (build mode only)
   ↓
Write public/frontalign.build.css

The PostCSS step merges :root and [fa-theme="dark"] blocks, removes duplicates, and normalizes token order in the final output. The layering step that follows is what guarantees custom always wins over utilities at equal specificity, no matter what order the source rules were written in.

Runtime Configuration for CDN Usage

For CDN projects or static pages where a build step is not used, FrontAlign also provides a runtime configuration API.

<script src="https://cdn.jsdelivr.net/npm/frontalign/dist/js/frontalign.min.js"></script>
<script>
  new FrontAlign({
    theme: {
      primary: '#2563eb',
      body: '#ffffff',
      bodyText: '#111827',
      dark: {
        body: '#0f172a',
        bodyText: '#e5e7eb'
      }
    },
    classes: {
      'hero-card': {
        'background': 'var(--body)',
        'color': 'var(--body-text)',
        'padding': '2rem'
      }
    },
    fonts: [
      {
        family: 'Inter',
        weights: '400;500;600;700',
        alias: 'inter'
      }
    ]
  });
</script>

The runtime engine can:

  • Inject light and dark theme variables
  • Generate custom class rules
  • Generate font utility classes
  • Inject Google Font stylesheet links
  • Add preconnect hints for Google Fonts

Responsive breakpoints (theme.breakpoints) are a build-time feature and are not available through the runtime configuration API — media queries require the JIT scan step to know which tiers are actually used, which only runs during npx frontalign build or npx frontalign dev.

Compiler vs Runtime Configuration

FeatureCompiler EngineRuntime Configuration
Best forNPM, Next.js, React, production appsCDN, static HTML, quick prototypes
Outputpublic/frontalign.build.css

Injected <style> and font links

Scans project filesYesNo
Extracts used utilitiesYesNo
Generates theme variablesYesYes
Generates custom classesYesYes
Generates font utilitiesYesYes
Generates responsive breakpointsYesNo
Production optimizationYesNo build step
Recommended for deploymentYesCDN / no-build workflows only

Automatic JIT Dependency Installation

FrontAlign's JIT engine relies on a handful of packages under the hood — postcss, cssnano, glob, and chokidar — but they are not bundled with the framework itself. The CLI checks for them automatically the first time you run npx frontalign build or npx frontalign dev, and installs anything missing before continuing, so you never have to install them by hand.

PackageUsed for
postcssParsing and transforming the compiled CSS AST
cssnanoProduction minification (npx frontalign build only)
globResolving jit.scan patterns to actual files
chokidarFile watching in npx frontalign dev

How detection works

  1. FrontAlign detects your package manager from the lockfile in your project root — pnpm-lock.yamlpnpm, yarn.lockyarn, otherwise npm.
  2. It verifies that manager's executable actually resolves on your system. If the lockfile points to a manager that isn't installed, FrontAlign silently falls back to npm.
  3. If a required package is missing, only that specific package is installed — not the full list — whenever Node can identify exactly which one is missing. If it can't, all four are installed together.
📦 FrontAlign: Installing missing JIT package(s) with npm: chokidar

After install

Because Node's ESM loader caches a failed import() for a given package even after it's installed moments later, FrontAlign does not retry the import in the same process. Instead, it re-runs the CLI command in a fresh Node process so module resolution starts clean.

If no supported package manager (npm, yarn, or pnpm) can be found on your system, or the automatic install itself fails, FrontAlign stops and prints the exact manual install command instead of retrying in a loop:

npm install -D postcss cssnano glob chokidar

Recommended Setup

For production applications, use the Compiler Engine:

bash npx frontalign build

For development:

bash npx frontalign dev

For CDN-only projects, use new FrontAlign({...}) runtime configuration.

FrontAlign