CSS System: Variables, Selectors and No Tailwind
Need this adapted for your organization? Work with AstroWeb.dev — Astro services by NViewsWEB.
Feature

CSS System: Variables, Selectors and No Tailwind

Plain CSS files, generated colour and size variables, light and dark themes by attribute selector, and a build that bundles, validates and minifies.

AstroWEBTheme does not use Tailwind. Styling is hand-written CSS plus a small set of generated variables and utility classes. Both editions work this way, which keeps the markup free of long class lists and the build free of a utility compiler.

Tokens come from config

Three files in src/site/config/styles/ generate the variables you use everywhere:

Config Generator Output
colors.mjs colors-css.mjs src/site/styles/main/colors.css: light and dark colour variables, plus bg-, col-, br- and filter- utilities
variables.mjs variables-css.mjs src/site/styles/main/variables.css: layout widths, radius, borders, shadows, transitions
fonts.mjs fonts-css.mjs Font faces and the size, weight and line-height tokens (see Self-hosted fonts)

To rebrand, change brand, accent and neutral in colors.mjs. Contrast pairs are chosen to meet WCAG AA.

Selectors

  • Theme by attribute. Colours are declared under :root, [data-theme="light"] and [data-theme="dark"]. The header toggle only changes data-theme on <html>, so every component follows without extra CSS.
  • Variables in components. Rules read tokens such as var(--on-brand-base) rather than raw colours.
  • Utilities. Small single-purpose classes live in core/styles/main/utilities-*.css (display, flex and grid, space, size, position, colours). Responsive variants carry a prefix such as lg- or full-.
  • Component state by data attribute. The callout uses .callout[data-variant="ctaPrimary"], so variants are styled by selector, not by new classes.

Branches and order

src/site/config/styles/styles.mjs is the single source for what goes where.

Branch Sources Output
inline core/styles/inline, src/site/styles/inline Generated TypeScript module, printed in the head
main core/styles/main, src/site/styles/main public/styles/main.[hash].css
components core/styles/components, src/site/styles/components Hashed bundle, loaded lazily
non-critical core/styles/non-critical, src/site/styles/non-critical Hashed bundle, loaded lazily
page CSS src/site/styles/pages/*.css public/styles/pages/<name>.[hash].css, requested per page with FilesToLoadCSS

Folders define membership. The order array only pins important files first. For a file with the same name, the core rules come first and the site rules follow, so your CSS wins through the normal cascade without !important.

Where PostCSS fits

postcss and autoprefixer are declared as dev dependencies, but the bundlers in core/scripts/build/css-bundles.mjs and css-page.mjs do not import them. Astro is configured with css.transformer: "lightningcss", and the bundlers use lightningcss to parse every source file (a syntax error stops the build with a line and column), then minify the combined bundle. If stylelint is installed, the minified output is linted too. If your project needs a PostCSS plugin, add it as a project step; the theme does not require one.

Commands

pnpm run generate-css        # run the whole pipeline
pnpm run generate-css:dry    # show inputs, outputs and stale steps; writes nothing
pnpm run generate-css:force  # rebuild every output

The order is: colours, fonts, variables, main, inline, components, page CSS, non-critical. Manifest data goes to src/data/manifest/styles.json. Details live in core/scripts/generate/css.md.

Where to put your CSS

  • Project styling: src/site/styles/main/ or src/site/styles/pages/.
  • Do not edit core/styles/ for one client; keep core reusable.
  • Never hand-edit colors.css, variables.css or fonts.css; they are regenerated.