Styling
ng-oat doesn't invent its own styling system. Everything is powered by Oat CSS design tokens โ plain CSS custom properties you can override anywhere.
How styling works
Oat CSS uses a layered architecture with @layer โ theme tokens come first, then base element styles, then component styles, then utilities. This means you can override any token in your own stylesheet without specificity wars.
ng-oat components render semantic HTML elements that Oat already knows how to style. A <ng-oat-button> renders a real <button>, a <ng-oat-card> renders a <div class="card">, and so on. This means Oat's CSS applies naturally โ no shadow DOM, no encapsulation issues.
Design tokens
Oat defines 80+ CSS custom properties (design tokens) that control colors, spacing, typography, borders, and animations globally. All tokens use light-dark() for automatic dark-mode support and clamp() for fluid scaling.
Colors
| Token | Purpose | Light / Dark |
|---|---|---|
--primary | Primary accent | #574747 / #fafafa |
--primary-foreground | Text on primary | #fafafa / #18181b |
--background | Page background | #fff / #09090b |
--foreground | Default text | #09090b / #fafafa |
--card | Card background | #fff / #18181b |
--card-foreground | Card text | #09090b / #fafafa |
--secondary | Secondary background | #f4f4f5 / #27272a |
--muted-foreground | Muted text | #71717a / #a1a1aa |
--accent | Hover / tint | #f4f4f5 / #27272a |
--border | Borders | #d4d4d8 / #52525b |
--danger | Error / destructive | #d32f2f / #f4807b |
--success | Success state | #008032 / #6cc070 |
--warning | Warning state | #a65b00 / #f0a030 |
--ring | Focus ring | #574747 / #d4d4d8 |
Spacing (fluid)
| Token | Value |
|---|---|
--space-1 | 0.25rem (4px) |
--space-2 | 0.5rem (8px) |
--space-3 | clamp(0.5rem, 1.5vw, 0.75rem) |
--space-4 | clamp(0.5rem, 2vw, 1rem) |
--space-5 | clamp(0.75rem, 2.5vw, 1.25rem) |
--space-6 | clamp(0.75rem, 3vw, 1.5rem) |
--space-8 | clamp(1rem, 4vw, 2rem) |
--space-10 | clamp(1.5rem, 5vw, 2.5rem) |
--space-12 | 3rem |
--space-14 | 3.5rem |
--space-16 | 4rem |
--space-18 | 4.5rem |
Border radius
| Token | Value |
|---|---|
--radius-small | 0.125rem |
--radius-medium | clamp(0.25rem, 0.8vw, 0.375rem) |
--radius-large | clamp(0.5rem, 1.5vw, 0.75rem) |
--radius-full | 9999px (pill) |
Typography
| Token | Value / Range |
|---|---|
--text-1 | clamp(1.75rem, โฆ, 2.25rem) โ largest heading |
--text-2 | clamp(1.5rem, โฆ, 1.875rem) |
--text-3 | clamp(1.25rem, โฆ, 1.5rem) |
--text-4 | clamp(1.125rem, โฆ, 1.25rem) |
--text-5 | clamp(1rem, โฆ, 1.125rem) |
--text-6 | 1rem โ base |
--text-7 | clamp(0.8125rem, โฆ, 0.875rem) โ small |
--text-8 | clamp(0.6875rem, โฆ, 0.75rem) โ smallest |
--text-regular | clamp(1rem, โฆ, 1.125rem) โ body text |
Line-height & letter-spacing
| Token | Value |
|---|---|
--leading-none | 1 |
--leading-tight | 1.25 |
--leading-snug | 1.375 |
--leading-normal | clamp(1.5, โฆ, 1.6) |
--leading-relaxed | 1.625 |
--leading-loose | 2 |
--tracking-tighter | -0.05em |
--tracking-tight | -0.025em |
--tracking-normal | 0em |
--tracking-wide | 0.025em |
--tracking-wider | 0.05em |
--tracking-widest | 0.1em |
Font weight & family
| Token | Value |
|---|---|
--font-normal | 400 |
--font-medium | 500 |
--font-semibold | 600 |
--font-bold | 700 |
--font-sans | system-ui, sans-serif |
--font-mono | ui-monospace, Consolas, monospace |
Effects
| Token | Value |
|---|---|
--shadow-small | 0 1px 2px rgb(0 0 0 / 0.05) |
--shadow-medium | 0 1px 3px rgb(0 0 0 / 0.1), โฆ |
--shadow-large | 0 4px 6px rgb(0 0 0 / 0.1), โฆ |
--transition-fast | 120ms cubic-bezier(0.4, 0, 0.2, 1) |
--transition | 200ms cubic-bezier(0.4, 0, 0.2, 1) |
Overriding tokens globally
The simplest way to customize the look is to override tokens in your styles.css:
/* src/styles.css */
:root {
--primary: #6366f1; /* Indigo primary */
--radius-medium: 12px; /* Rounder cards */
--font-bold: 700; /* Heavier bold weight */
} Because Oat uses @layer, your overrides in styles.css will always win regardless of source order.
Runtime theming with provideNgOatTheme
For dynamic themes (dark mode toggle, user-selected palettes), use the built-in theme provider:
// app.config.ts
import { provideNgOat, provideNgOatTheme } from '@letsprogram/ng-oat';
export const appConfig: ApplicationConfig = {
providers: [
provideNgOat(),
provideNgOatTheme({
tokens: {
'--oat-primary': '#6366f1',
'--oat-background': '#fafafa',
'--oat-radius-medium': '12px',
},
}),
],
};Then inject NgOatThemeRef to change tokens at runtime:
// any component
import { Component, inject } from '@angular/core';
import { NgOatThemeRef } from '@letsprogram/ng-oat';
@Component({ ... })
export class SettingsPage {
private theme = inject(NgOatThemeRef);
switchToOcean() {
this.theme.setTokens({
'--oat-primary': '#0ea5e9',
'--oat-background': '#0f172a',
'--oat-foreground': '#f8fafc',
});
}
resetTheme() {
this.theme.reset();
}
}Dark mode
Oat CSS has built-in dark mode support via color-scheme. You can toggle it by setting the color-scheme property on the document root:
// In your root component
import { Component, inject, signal } from '@angular/core';
import { DOCUMENT } from '@angular/common';
@Component({ ... })
export class App {
private doc = inject(DOCUMENT);
isDark = signal(false);
toggleTheme() {
this.isDark.update(v => !v);
this.doc.documentElement.style.colorScheme =
this.isDark() ? 'dark' : 'light';
}
}Oat automatically adjusts all token values when the color scheme changes. No need to define separate dark token values yourself โ it's handled by the framework.
Scoped overrides
Since tokens are CSS custom properties, you can scope them to any container:
/* Darker sidebar section */
.sidebar {
--primary: #10b981;
--background: #0f172a;
--foreground: #e2e8f0;
--border: #334155;
} Any ng-oat component rendered inside .sidebar will pick up those overrides automatically. This is useful for sections with different visual treatments.
Oat's CSS layers
Oat organizes its CSS into layers for clean override behavior:
/* Oat's internal layer order */
@layer theme, base, components, animations, utilities;
/* theme โ CSS custom property definitions */
/* base โ element-level styles (h1, p, button, table, etc.) */
/* components โ card, dialog, sidebar, accordion, etc. */
/* animations โ keyframes and transitions */
/* utilities โ helper classes (.hstack, .mt-4, etc.) */ Your app styles (outside any @layer) automatically take precedence over all Oat layers. This is why simple token overrides in styles.css just work.
Utility classes
Oat provides a comprehensive set of utility classes for layout, spacing, typography, and responsive design. Here's a quick summary โ see the Utilities page for the full reference.
| Category | Classes |
|---|---|
| Flex | .hstack / .vstack / .flex / .flex-col / .flex-wrap |
| Alignment | .items-center / start / end ยท .justify-center / between / end / start |
| Gap | .gap-1 through .gap-8 + responsive sm:gap-*, md:gap-* |
| Margin | .mt-0 / 2 / 4 / 6 / 8 ยท .mb-0 / 2 / 4 / 6 / 8 |
| Padding | .p-0 / 2 / 4 / 6 / 8 ยท .px-2 / 4 ยท .py-2 / 4 + responsive |
| Width | .w-100 / .w-auto / .max-w-sm / md / lg / xl |
| Display | .d-none / block / flex / grid / inline / inline-block + responsive |
| Typography | .text-1โ.text-8, colors, weights, alignment, tracking, leading, transform, decoration, wrapping, clamp |
| Grid | .row ยท .col-1โ.col-12 + responsive sm: / md: / lg: / xl: |
Next up
Now that you understand how styling works, head over to the Usage guide to learn how to work with components, directives, and Signal Forms effectively.