# Theme System Source: /docs/theming/overview.md # Theme System Frontile's theme system is an accessible design foundation built on semantic colors, a surface system, and automatic dark mode support. ## Installation Install the theme package using Ember CLI: ```bash ember install @frontile/theme ``` ## Configuration ### Using Default Theme Add this to your `app/styles/app.css`: ```css title="app/styles/app.css" @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; @import "@frontile/theme"; ``` ### Custom Theme Configuration Create `frontile.js` in your project root: ```js title="frontile.js" const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ /* your configuration */ }); ``` Then update `app/styles/app.css`: ```css title="app/styles/app.css" @import 'tailwindcss' source('../../'); @plugin "./../../frontile.js"; @import "@frontile/theme"; ``` ## Key Concepts ### Semantic Colors Meaningful color categories (neutral, primary, success, danger, warning) with intuitive levels (subtle, soft, firm, strong) that adapt between light and dark themes. ### Surface System Flexible depth system with opaque surface roles and two families of translucent veils — overlay (recedes) and lift (advances), each with subtle, soft, mild, firm, strong. ### Dark Mode Automatic theme adaptation with color inversion. Toggle the `dark` class to switch between light and dark modes. ## Quick Example Frontile components resolve semantic colors automatically: ```gts preview import { Button } from 'frontile'; ``` The Button already resolves its own semantic colors, so it adapts to light and dark themes without additional code. Under the hood the `primary` color provides the emphasis, while `on-primary` calculates the optimal contrasting text color (black or white) for accessibility — the same tokens you'd reach for on your own markup: ```gts preview ``` ## Theme Switching Toggle between light and dark themes by adding or removing the `dark` class on a parent element (typically `` or ``): ```html ``` Remove the `dark` class to revert to light theme. You can also explicitly use the `light` class if needed. ### How Theme Classes Work Frontile uses custom CSS variants that apply theme-specific styles: - **`.dark`** - When present on a parent element, all descendant elements use dark theme colors - **`.light`** - When present on a parent element, all descendant elements use light theme colors (default) - **`.theme-inverse`** - Inverts the current theme for a section and its descendants The theme system uses CSS selectors to ensure proper inheritance: ```css /* Light theme applies to .light elements and .theme-inverse within .dark */ .light *, .dark .theme-inverse * /* Dark theme applies to .dark elements and .theme-inverse within .light */ .dark *, .light .theme-inverse * ``` This means you can nest themes and use theme-inverse sections anywhere in your application, and the colors will automatically adapt. ## Theme Inverse Create sections with inverted theme colors using the `theme-inverse` utility class. In light mode, these sections display dark theme colors, and vice versa. ```gts preview import { Button } from 'frontile'; ``` [Learn more about theme-inverse →](configuration/theme-switching.md) ## Color System Frontile provides six semantic color categories: - **Neutral** - Default interface colors - **Primary** - Primary brand colors (blue) - **Accent** - Visual emphasis and highlights (violet) - **Success** - Positive states and actions (green) - **Danger** - Errors and destructive actions (red) - **Warning** - Warnings and cautions (orange) Each category spans two bands: a **surface band** of fills (`subtle`, `muted`, `soft`, `mild`, `DEFAULT`, `firm`) and an **ink band** of legible foregrounds (`strong`, `bolder`). The `on-{color}-{level}` prefix automatically provides optimal contrasting text colors (black or white) for accessibility. ### How Colors Adapt to Themes Colors automatically adapt based on the current theme (`.dark`, `.light`, or `.theme-inverse`). Frontile uses CSS variables that change their values based on the theme context: ```css /* Example: primary adapts to the theme */ .light { --color-primary: oklch(55.86% 0.2094 263.84); /* Vivid teal in light mode */ } .dark { --color-primary: oklch(69.83% 0.1526 252.37); /* Lighter teal in dark mode */ } ``` When you use `bg-primary`, the actual color value comes from the CSS variable, which automatically switches based on whether the element is in a `.light` or `.dark` context. ### Using Colors in Your CSS You can use semantic colors in custom CSS using the Tailwind `theme()` function or CSS variables: ```css /* Using Tailwind theme function */ .my-component { background-color: theme(colors.primary.DEFAULT); color: theme(colors.on-primary.DEFAULT); } /* Using CSS variables directly */ .my-component { background-color: var(--color-primary); color: var(--color-on-primary); } ``` ### Customizing Colors You can customize semantic colors using the JavaScript plugin configuration: ```js const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ themes: { light: { colors: { primary: { subtle: '#eff6ff', soft: '#93c5fd', DEFAULT: '#3b82f6', strong: '#1e40af' } } }, dark: { colors: { primary: { subtle: '#1e3a8a', soft: '#3b82f6', DEFAULT: '#60a5fa', strong: '#dbeafe' } } } } }); ``` [Learn more about semantic colors →](design-tokens/colors.md) ## Surface System The Surface system provides two types of backgrounds: - **Surface Solid** - Opaque base layers (0-11 scale) - **Surface Overlay** - Translucent layers that recede into the page (subtle, soft, mild, firm, strong) - **Surface Lift** - Translucent layers that float above the page (subtle, soft, mild, firm, strong) [Learn more about the surface system →](design-tokens/surfaces.md) ## Basic Customization ### Customizing Colors Override semantic colors in your configuration: ```js const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ themes: { light: { colors: { primary: { subtle: '#eff6ff', soft: '#93c5fd', DEFAULT: '#3b82f6', strong: '#1e40af' } } }, dark: { colors: { primary: { subtle: '#1e3a8a', soft: '#3b82f6', DEFAULT: '#60a5fa', strong: '#dbeafe' } } } } }); // Note: on-{color}-{level} classes are automatically generated // based on the background colors you define above ``` ### Setting Default Theme Specify which theme loads by default: ```js module.exports = frontile({ defaultTheme: 'light', // or 'dark' themes: { // ... your themes } }); ``` ## Advanced Configuration ### Customizing with CSS Variables The recommended approach for Tailwind v4 is to customize theme values directly using CSS variables. This provides fine-grained control without JavaScript configuration and better performance. #### Override Theme Defaults Add your customizations in your `app/styles/app.css` after importing the theme: ```css title="app/styles/app.css" @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; @import "@frontile/theme"; /* Override Tailwind theme utilities */ @theme { /* Custom border radius values */ --radius: 0.75rem; /* One knob: scales the whole rounded-* scale */ --radius-pill: 2rem; /* Custom pill radius (absolute, does not scale) */ /* Custom opacity values */ --opacity-hover: .9; --opacity-disabled: .4; } /* Override component-specific values */ :root { /* Modal sizes */ --modal-md: 32rem; --modal-lg: 48rem; /* Drawer sizes */ --drawer-md: 32rem; --drawer-lg: 48rem; } ``` #### Theme-Specific Customization Customize layout values for specific themes using the `.dark` and `.light` classes. **Important:** To properly target all instances of a theme including `.theme-inverse` sections, use both selectors: ```css @import "@frontile/theme"; /* Light theme customization - targets .light AND .theme-inverse within .dark */ .light, .dark .theme-inverse { --opacity-hover: .85; } /* Dark theme customization - targets .dark AND .theme-inverse within .light */ .dark, .light .theme-inverse { --opacity-hover: .75; --modal-lg: 56rem; } ``` **Why both selectors?** - `.light` targets elements in light mode - `.dark .theme-inverse` targets inverted sections within dark mode (which display light theme) - This ensures your customizations apply consistently across both regular theme usage and theme-inverse sections > **Note:** Variables in the `@theme` block automatically generate Tailwind utilities (e.g., `--radius-xl` creates `rounded-xl` class). Variables in `:root` or theme-specific selectors are for component-specific values that don't need utility classes. **Colors should be customized using the JavaScript plugin configuration, not CSS variables.** ### JavaScript Configuration You can also configure the theme using JavaScript by creating a `frontile.js` file in your project root: ```js title="frontile.js" collapsible const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ defaultTheme: 'dark', themes: { light: { colors: { // Customize semantic colors primary: { subtle: '#eff6ff', soft: '#93c5fd', DEFAULT: '#3b82f6', strong: '#1e40af' } }, layout: { // Customize layout values opacity: { hover: .85, disabled: .4 }, radius: { DEFAULT: '0.75rem', pill: '2rem' } } }, dark: { colors: { primary: { subtle: '#1e3a8a', soft: '#3b82f6', DEFAULT: '#60a5fa', strong: '#dbeafe' } } } } }); ``` Then reference it in your CSS: ```css @import 'tailwindcss' source('../../'); @plugin "./../../frontile.js"; @import "@frontile/theme"; ``` ## Best Practices ### Use Semantic Colors Always prefer semantic color classes over generic Tailwind utilities: ```hbs {{! Good - uses semantic colors }} {{! Avoid - hard-coded colors don't adapt to theme }} ``` ### Use Surface System for Backgrounds Use the surface system for component backgrounds: ```hbs {{! Good - uses a surface role for the base }}
{{! Good - uses overlay for elevated surface }}
Card content
``` ### Test in Both Modes Always verify your UI in both light and dark modes: ```html ``` --- # Component Styles Source: /docs/theming/component-styles.md # Customizing Component Styles Frontile components can be customized with Tailwind Variants, either globally or per-instance. This guide covers both approaches. ## Overview Frontile uses [Tailwind Variants](https://www.tailwind-variants.org/) to manage and customize component styles. Tailwind Variants provide a structured way to define different visual representations (or variants) for components while keeping your styles consistent and maintainable. A **slot** represents a specific part of a component that can be styled or customized separately. Slots allow you to apply different styles to different parts of a complex component, providing more granular control. When customizing styles in Frontile, you can either apply styles globally using `registerCustomStyles` or customize individual components using class arguments. You can override default component styles by passing your own class names to the `class` or `classes` argument, depending on whether the component has slots. ## DOM Anatomy: `data-component` and `data-part` Every Frontile component renders a stable, documented DOM anatomy, independent of its CSS classes. Two attributes carry it: - **`data-component=""`** — the kebab-cased `tv()` config name (e.g. the `notificationCard` config produces `notification-card`), written on the component's **outermost rendered element only**. This is the name the component is registered under, not its filename — `commandDialog` → `command-dialog`. - **`data-part=""`** — the kebab-cased `tv()` slot key, written on **every** element that renders a slot (`startContent` → `start-content`). Slot names are exactly the keys you already pass to `@classes`, so if you can style a part with `@classes={{hash startContent='...'}}`, you can also select it with `[data-part="start-content"]`. The root element's part is usually `base`, but not always — it carries whichever slot it actually renders. `Table`'s root is its wrapper `
`, so that element is `data-part="wrapper"`; `SimpleTable` used on its own roots on the `` element, so that one is `data-part="table"`. `ProgressBar`'s outer `
` renders no slot at all, so it has `data-component="progress-bar"` and no `data-part`. Check the component's page if you need the exact shape. Both attributes can be overridden: a `data-component` or `data-part` you pass to a component wins over the one it would render itself. A nested component's root legitimately carries **both** its own `data-component` and a `data-part` belonging to its parent. `CloseButton` rendered inside `Alert`, for instance, is simultaneously `data-component="close-button"` (its own root) and `data-part="close-button"` (the part it fills in `Alert`'s anatomy). Two components can also render the same `data-component` value: `TabNav` is a second renderer of the `tabs` config, alongside `Tabs` itself. ### Selecting by anatomy Scope a selector to one component's own parts with: ```css [data-component="modal"] [data-part="header"] { /* ... */ } ``` ### Known limitation: a nested component can share a part name `[data-component="x"] [data-part="y"]` is a plain CSS descendant combinator — it matches **any** descendant, not just `y` parts that belong directly to `x`. That's ambiguous whenever a nested component happens to have a part with the same name. `Alert` renders a `CloseButton`, and `CloseButton` has its own `icon` part (its SVG); `Alert` also has its own `icon` part (the alert's leading icon). `[data-component="alert"] [data-part="icon"]` matches both — the alert's own icon *and* the close button's icon glyph, because the close button is a descendant of the alert's root. What you usually mean is "the parts whose nearest `[data-component]` ancestor is `x`". CSS has no "nearest enclosing" combinator, so in a stylesheet you narrow the selector yourself, using child combinators down the path you want: ```css /* The alert's own icon. The close button's icon is nested one level deeper, inside the close button, so it doesn't match. */ [data-component="alert"] > [data-part="inner"] > [data-part="icon"] { /* ... */ } ``` In tests, use the `ownParts(root, part)` helper from `frontile/test-support`, which returns only the parts belonging to `root` itself: ```ts import { ownParts } from 'frontile/test-support'; // Only Alert's own `icon` part, not CloseButton's. const icons = ownParts(alertRootElement, 'icon'); ``` ### Not `data-slot` If you're coming from shadcn/ui, Nuxt UI, or HeroUI, this convention will look familiar but the attribute name differs — those use `data-slot`, Frontile uses `data-part`. In Ember, "slot" already means a named block (`{{yield to="title"}}`), which is a different thing from a styleable piece of the DOM. `@frontile/forms-legacy` predates this convention and does not carry these attributes. ## Customizing Styles ### Setting Up Global Customization To apply global styles to your Frontile components, it's recommended to create a separate file for your theme settings. You can create a file named `app/theme.js` to register your global custom styles, and then import it in `app/app.js` to ensure the styles are applied across your application. #### Example: Setting Up Global Styles 1. **Create a Theme File**: Create a new file called `app/theme.js` and add your global customizations. ```javascript // app/theme.js import { useStyles, registerCustomStyles, tv } from '@frontile/theme'; const components = useStyles(); registerCustomStyles({ // stuff ... }); ``` 2. **Import the Theme File**: Import the theme file in `app/app.js` to ensure the global styles are applied. ```javascript // app/app.js import Application from '@ember/application'; // stuff... import './theme'; // Import your theme file here export default class App extends Application { // stuff... } loadInitializers(App, config.modulePrefix); ``` ### Global Customization with `registerCustomStyles` The `registerCustomStyles` function allows you to override the default styles of Frontile components globally. This means that every instance of a specific component throughout your application will inherit the styles defined in `registerCustomStyles`. #### Example: Global Customization To globally change the styles of the Drawer, Modal, and Button components: ```javascript import { useStyles, registerCustomStyles, tv } from '@frontile/theme'; const components = useStyles(); registerCustomStyles({ modal: tv({ extend: components.modal, slots: { header: 'font-header text-2xl pt-12 pl-6 pb-6', body: 'pt-4 pb-4 pl-6 pr-12', footer: 'bg-transparent py-4 px-12 pb-8' }, variants: { size: { lg: 'max-w-[48rem]', xl: 'max-w-[64rem]' } } }), drawer: tv({ extend: components.drawer, slots: { header: 'text-2xl px-4 py-6 bg-black text-white dark:bg-white dark:text-black rounded-none', footer: 'px-4 py-6' }, variants: { size: { lg: 'max-w-[48rem]', xl: 'max-w-[64rem]' } } }), button: tv({ ...components.button, base: ['font-header text-xl'], variants: { ...components.button.variants, appearance: { ...components.button.variants.appearance, default: 'shadow-elevation-2' } }, compoundVariants: [ ...components.button.compoundVariants, { appearance: 'default', intent: 'default', class: 'bg-black text-white hover:bg-black/80 dark:bg-white dark:text-black dark:hover:bg-white/80' } ] }) }); ``` In this example: - The `modal` component is customized globally, including changes to the header, body, and footer slots, and additional size variants. - The `drawer` component is updated with new styles for the header and footer slots, as well as size variants. - The `button` component's base styles and compound variants are customized, adding new default appearances. Use global customization when you want consistency across your entire application for a particular component. ### Local Customization with Component Arguments If you only need to customize the styles of a specific component instance, use arguments like `class` or `classes`, depending on the component. Frontile uses Tailwind Variants with `tw-merge` to merge Tailwind classes, so any class passed locally overwrites the default styles. #### Example: Local Customization with Drawer Component ```hbs ``` In this example, the `Drawer` component is provided with specific styles for the `header`, `body`, and `footer` slots. The `header` and `footer` have a dark background (`bg-neutral-strong`) with automatically contrasting text (`text-on-neutral-strong`), while the `body` has a light background (`bg-neutral-subtle`). This approach allows for context-specific customizations without impacting other instances of the Drawer component. --- # Design Tokens Overview Source: /docs/theming/design-tokens/overview.md # Design Tokens Frontile extends Tailwind CSS with additional design tokens for building consistent, accessible user interfaces. These tokens automatically generate Tailwind utility classes. ## What are Design Tokens? Design tokens are named entities that store visual design attributes. They provide a consistent language for design decisions across your application. Frontile's tokens use Tailwind v4's `@theme` directive to define CSS variables that automatically generate utility classes. For example, defining `--radius: 8px` in your theme automatically generates the `rounded` utility class. ## Token Categories Frontile adds these design token categories to Tailwind: ### [Colors](colors.md) Semantic color system with five categories (neutral, primary, success, danger, warning) that automatically adapt between light and dark themes. **Generated utilities:** `bg-primary`, `text-danger-strong`, `border-success-soft` ### [Typography](typography.md) Text styles with semantic sizing across six categories (marquee, header, body, code, caption, label) built on a modular scale. **Generated utilities:** `text-header-lg`, `text-body-md`, `text-label-sm` ### [Icons](icons.md) Consistent icon sizing scale that pairs harmoniously with typography. **Generated utilities:** `size-icon-sm`, `size-icon-md`, `size-icon-lg` ### [Borders](borders.md) Border widths and radius tokens for consistent UI styling. **Generated utilities:** `border-thin`, `border-heavy`, `rounded-xl`, `rounded-pill` ### [Elevation](elevation.md) Shadow system for creating visual depth with six elevation levels (0-5). **Generated utilities:** `shadow-elevation-1`, `shadow-elevation-3` ### [Surfaces](surfaces.md) Background color system for creating distinct surface layers in your UI. **Generated utilities:** `bg-surface-app`, `bg-surface-modal`, `bg-surface-overlay-soft`, `bg-surface-lift-soft` ## How Tokens Work Frontile uses Tailwind v4's `@theme` directive to define tokens as CSS variables: ```css @theme { --radius: 8px; /* Generates: rounded */ --radius-xl: 12px; /* Generates: rounded-xl */ --size-icon-md: 16px; /* Generates: size-icon-md */ } ``` These variables: - Automatically generate Tailwind utility classes - Can be customized globally or per-theme - Maintain consistency across your application - Support light and dark theme variations ## Standard Tailwind Utilities For layout, spacing, flexbox, grid, and other standard utilities, refer to the [Tailwind CSS documentation](https://tailwindcss.com/docs). Frontile uses Tailwind as-is for these utilities and only documents the design tokens it adds. --- # Colors Source: /docs/theming/design-tokens/colors.md # Semantic Colors Frontile's semantic color system provides meaningful, accessible colors that adapt automatically between light and dark themes. ## Overview Instead of using arbitrary gray scales like `bg-gray-300`, Frontile's semantic colors use meaningful names that describe their purpose and visual weight. Each color automatically adapts between light and dark themes, ensuring consistent contrast and accessibility. ## Color Levels Every category exposes the same set of levels, organized into **two bands**. The bands share one emphasis vocabulary but are consumed by different CSS properties — the band a level belongs to tells you what it's for. | Band | Levels (low → high emphasis) | Use for | | ----------- | ----------------------------------------------------------- | ------------------------------------------------ | | **Surface** | `subtle` · `muted` · `soft` · `mild` · `DEFAULT` · `firm` | Fills — `bg-*` and decorative `border-*` | | **Ink** | `strong` · `bolder` | Legible foregrounds — `text-*`, outlined borders | - **`DEFAULT`** is the resting fill. It has no suffix, so the bare class works: `bg-primary` is the DEFAULT fill, `text-primary` the DEFAULT-level text. - **`mild`** sits between `soft` and `DEFAULT` — a lower-emphasis fill for cases where `soft` reads too faint but the full resting fill is too strong. - **`firm`** is the most emphatic _fill_ (e.g. a pressed background). It sits at the top of the surface band, below the ink band it never competes with — `firm` is a background, `strong`/`bolder` are text, so they live in different property slots even though they share the ladder. - **`on-{category}-{level}`** is the auto-generated black/white text that meets WCAG contrast on that fill (e.g. `text-on-primary` on `bg-primary`). ### Names are ranks, not brightness A level name describes **emphasis rank**, never a specific lightness. The same token can be dark in light mode and light in dark mode, because interaction direction inverts between schemes (light mode lightens on hover and darkens on press; dark mode does the reverse). A brightness word like "deep" or "light" would be correct in one theme and wrong in the other, so the vocabulary stays abstract. ### Why two bands instead of one numbered scale Tailwind maps one token name to exactly one color value, reused across every property (`bg-primary`, `text-primary`, and `border-primary` are always the same color). A fill and the text that must stay legible on top of it are different colors, so they can't share a single rung — they need two names. The band split makes that boundary explicit instead of hiding it inside a flat `50…900`-style scale. ## Color Categories ### Neutral **Usage Examples:** Text colors: - `text-neutral-firm` - Secondary text for descriptions and metadata - `text-neutral-strong` - Default text color for body content - `text-neutral-bolder` - Maximum emphasis for headings Background colors with automatic text colors: - `bg-neutral-subtle` with `text-on-neutral-subtle` - Subtle background for hover states - `bg-neutral-soft` with `text-on-neutral-soft` - Soft background for cards - `bg-neutral` with `text-on-neutral` - Medium emphasis background - `bg-neutral-strong` with `text-on-neutral-strong` - Strong emphasis background ### Primary **Usage Examples:** ```gts preview collapsible ``` Banners: ```gts preview ``` ### Accent **Usage Examples:** ```gts preview ``` ### Success **Usage Examples:** ```gts preview ``` ### Danger **Usage Examples:** ```gts preview ``` ### Warning **Usage Examples:** ```gts preview ``` ## Choosing the Right Color Level Pick from the band that matches the property you're setting. ### Backgrounds & decorative borders (surface band) - **`subtle`**: Hover states, selected rows, tonal/hairline backgrounds - **`muted`**: Hover on tonal fills, chip and close-button backgrounds - **`soft`**: Card backgrounds, secondary surfaces, the hover step for solid fills - **`DEFAULT`** (bare `bg-{color}`): Primary buttons, badges, tags, resting borders - **`firm`**: Pressed/active backgrounds ### Text & outlined borders (ink band) - **`strong`**: Default body text, readable content, outlined-control text and borders - **`bolder`**: Headings, hover/active text, highest-emphasis foregrounds - **`on-{color}-{level}`**: Automatic contrasting text on a colored fill (e.g., `text-on-primary` on `bg-primary`) > **Note:** Surface-band levels (`subtle` … `firm`) are fills, not text colors. > In dark mode they map to values that are illegible as text on dark surfaces. > For visible text use an ink-band level (`strong`/`bolder`) or the matching > `on-{color}` token. ## Accessibility All color combinations in Frontile meet WCAG 2.1 Level AA contrast requirements (minimum 4.5:1 contrast ratio). ### Automatic "On-" Colors Frontile automatically generates optimal contrasting text colors for every background color using WCAG contrast calculations. Use the `on-` prefix with the same color level: ```html ``` **How it works:** 1. Frontile calculates the relative luminance of each background color 2. Determines contrast ratio with both black and white text 3. Selects the color with better contrast (≥4.5:1 ratio) 4. Generates the appropriate `on-{color}-{level}` class automatically **Benefits:** - No need to manually determine text colors - Always meets WCAG AA standards - Automatically adapts to theme changes (light/dark mode) - Works with custom theme colors ### Customizing On-Colors While auto-generated on-colors work for most cases, you can override them for brand consistency or specific design requirements: ```js const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ themes: { light: { colors: { primary: { subtle: '#eff6ff', soft: '#93c5fd', DEFAULT: '#3b82f6', strong: '#1e40af' }, // Override specific on-colors 'on-primary': { DEFAULT: '#ffffff', // Force white text on the bare primary fill strong: '#e0f2fe' // Use light blue instead of auto-generated white } // on-primary-subtle and on-primary-soft are still auto-generated } } } }); ``` Partial overrides are supported — only define the levels you want to customize, and the rest will be auto-generated as usual. This works for all semantic color categories: `on-neutral`, `on-primary`, `on-secondary`, `on-tertiary`, `on-success`, `on-warning`, `on-danger`, and `on-surface-modal`. > **Note:** If a color value is a CSS variable reference (e.g., `var(--my-color)`), auto-generation is skipped for that color since contrast cannot be calculated at build time. ## Migrating from Old Colors If you're upgrading from an older version of Frontile, see the [Semantic Colors v2 Migration Guide](../../migrations/v0.18/semantic-colors.md) for detailed instructions on updating your code. --- # Typography Source: /docs/theming/design-tokens/typography.md # Typography System Frontile's typography system is a scalable type system built on semantic text styles and a modular scale. It uses Tailwind v4's `@theme` directive for base tokens and `@layer utilities` for explicit utility classes. ## Overview The typography system includes: - **7 text style categories**: Marquee, Header, Strong, Body, Code, Caption, and Label - **Multiple size variants** for each category - **Modular scale** based on a mathematical ratio for harmonic sizing - **Composite utility classes** that bundle all typography properties together - **Full customization** through CSS variables ## Text Style Categories ### Marquee Large, semibold serif display text (Domine) for hero sections and prominent headings. ```gts preview ``` **Available sizes:** `5xs`, `4xs`, `3xs`, `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` **Font:** Domine (serif) · **Weight:** semibold · **Best for:** Hero sections, landing page titles, major marketing headings ### Header Semantic headings for content hierarchy with bold weight; larger sizes (`lg` and up) tighten tracking and leading. ```gts preview ``` **Available sizes:** `4xs`, `3xs`, `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` **Best for:** Content headings (h1-h6), section titles, hierarchical structure ### Strong Emphasis text for numerics, prices, and metrics. Shares the Header size, tracking, and leading scale but is a distinct role so it can be styled independently. ```gts preview ``` **Available sizes:** `4xs`, `3xs`, `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` **Font:** Open Sans · **Weight:** bold · **Best for:** Prices, counts, metrics, any numeric emphasis ### Body Standard body text with spacious line height optimized for readability. ```gts preview collapsible ``` **Available sizes:** `5xs`, `4xs`, `3xs`, `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` **Best for:** Paragraphs, article content, descriptions, general text ### Code Monospace text for code snippets and technical content. ```gts preview ``` **Available sizes:** `sm`, `md` **Best for:** Code blocks, inline code, technical identifiers ### Caption Secondary descriptive text with relaxed letter spacing. ```gts preview ``` **Available sizes:** `sm`, `md` **Best for:** Image captions, figure labels, supplementary text ### Label UI labels and form labels with semibold weight and tight (100%) leading. ```gts preview ``` **Available sizes:** `nano`, `micro`, `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl` **Best for:** Form labels, UI labels, buttons, navigation items, badges ## Available Utilities All text style utilities require both a font-family utility and a sized text style utility: ```html

...

...

...

... ... ``` ## Combining with Other Utilities Typography utilities work with other Tailwind utilities: ```gts preview ``` ## How It Works The typography system uses Tailwind v4's `@theme` directive to automatically generate utilities from CSS variables: 1. **Base tokens** defined in `@theme` automatically generate individual utilities: - `--font-header` → `font-header` utility - `--font-size-14` → `text-14` utility - `--font-weight-bold` → `font-bold` utility - `--letter-spacing-tight` → `tracking-tight` utility - `--font-weight-semibold` → `font-semibold` utility 2. **Composite text style tokens** generate size/weight/spacing utilities (but NOT font-family): - `--text-header-md` → `text-header-md` utility (sets size, weight, spacing, line-height) - `--text-body-lg` → `text-body-lg` utility (sets size, weight, spacing, line-height) **Usage pattern:** Combine font-family with text style for complete typography: ```html

Heading Text

Body text with larger size

Small label

Heading Text

``` The separation of font-family from text styles provides flexibility to mix font families with different text styles while maintaining consistent sizing, weight, spacing, and line-height. ## Customization ### Using CSS Variables Customize typography by overriding CSS variables in your app's CSS file: ```css @import "@frontile/theme"; /* Override font families */ @theme { --font-header: "Inter", system-ui, sans-serif; --font-body: "Inter", system-ui, sans-serif; --font-code: "JetBrains Mono", monospace; } ``` ### Theme-Specific Typography Customize for light and dark themes using theme selectors: ```css /* Light theme customization */ .light, .dark .theme-inverse { --font-body: "Inter", system-ui, sans-serif; } /* Dark theme customization */ .dark, .light .theme-inverse { --font-body: "Geist", system-ui, sans-serif; } ``` ## Using Custom Fonts Add custom fonts by declaring them with `@font-face` and overriding the font family variables: ```css @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-weight: 100 900; font-display: swap; } @theme { --font-header: 'Inter', system-ui, sans-serif; --font-body: 'Inter', system-ui, sans-serif; --font-label: 'Inter', system-ui, sans-serif; } ``` ## Best Practices ### Use Semantic Text Styles Always prefer semantic text style utilities over combining individual properties: ```gts preview ``` ### Maintain Visual Hierarchy Use size variants to create clear content hierarchy: ```gts preview ``` ### Responsive Typography Scale typography for different screen sizes: ```gts preview ``` --- # Icon Sizes Source: /docs/theming/design-tokens/icons.md # Icon Sizes Frontile provides an icon sizing scale that pairs with typography for consistent visual alignment. ## Overview Icon sizes ensure visual consistency between icons and text throughout your interface. They use the same modular scale as typography, making it easy to pair icons with text styles. The scale ranges from `pico` (smallest) to `mega` (largest), providing 14 distinct sizes for different UI contexts. ## Using Icon Sizes Apply icon sizes using the `size-icon-*` utilities, which set both width and height: ```gts preview import { StarIcon } from 'site/components/icons'; ``` ## Available Sizes | Size | Utility | Usage Context | |------|---------|---------------| | Pico | `size-icon-pico` | Extremely small UI elements | | Nano | `size-icon-nano` | Tiny decorative icons | | Micro | `size-icon-micro` | Very small inline icons | | 3XS | `size-icon-3xs` | Extra extra small icons | | 2XS | `size-icon-2xs` | Extra small icons | | XS | `size-icon-xs` | Small inline icons | | SM | `size-icon-sm` | Small UI icons | | MD | `size-icon-md` | Default icon size | | LG | `size-icon-lg` | Large prominent icons | | XL | `size-icon-xl` | Extra large icons | | 2XL | `size-icon-2xl` | Double extra large icons | | 3XL | `size-icon-3xl` | Triple extra large icons | | Kilo | `size-icon-kilo` | Very large decorative icons | | Mega | `size-icon-mega` | Largest decorative icons | ## All Icon Sizes Here are all available icon sizes with visual examples: ```gts preview collapsible import { CheckIcon } from 'site/components/icons'; ``` ## Pairing with Typography Icon sizes are designed to pair harmoniously with text styles. Use these recommended pairings for visual alignment: ```gts preview import { CheckIcon, ViewIcon, StarIcon } from 'site/components/icons'; ``` ### Recommended Pairings | Text Style | Recommended Icon Size | |------------|----------------------| | `text-body-sm` | `size-icon-xs` or `size-icon-sm` | | `text-body-md` | `size-icon-sm` or `size-icon-md` | | `text-body-lg` | `size-icon-md` or `size-icon-lg` | | `text-header-sm` | `size-icon-md` | | `text-header-md` | `size-icon-md` or `size-icon-lg` | | `text-header-lg` | `size-icon-lg` or `size-icon-xl` | | `text-header-xl` | `size-icon-xl` or `size-icon-2xl` | ## Customization Override icon sizes using CSS variables in your `@theme` block: ```css @import "@frontile/theme"; @theme { /* Adjust specific sizes */ --size-icon-sm: 14px; --size-icon-md: 18px; --size-icon-lg: 22px; } ``` For more customization options, see the [Configuration Guide](../configuration/customization.md). --- # Borders & Radius Source: /docs/theming/design-tokens/borders.md # Borders & Radius Frontile provides border width and radius tokens for consistent UI styling across your application. ## Border Widths Control border thickness with semantic width tokens. These tokens provide meaningful names that describe the visual weight of borders. ### Available Widths ```gts preview ``` | Width | Utility | Common Uses | |-------|---------|-------------| | Thin | `border-thin` | Subtle dividers, table borders, minimal separation | | Default | `border` | Standard card borders, input fields, default UI elements | | Heavy | `border-heavy` | Focus states, selected items, emphasized borders | | Aggressive | `border-aggressive` | High-contrast borders, debug indicators, strong emphasis | ### Border Width Examples ```gts preview ``` ## Border Radius Control corner rounding with radius tokens that range from sharp edges to fully rounded elements. Every step is a multiple of a single `--radius` base (`0.5rem` by default), so the scale is proportional rather than arbitrary. Changing `--radius` alone re-rounds the entire library — see [Scaling the whole system](#scaling-the-whole-system) below. ### Available Radii ```gts preview collapsible ``` | Radius | Utility | Multiplier | Default | Common Uses | |--------|---------|-----------|---------|-------------| | None | `rounded-none` | — | 0px | Sharp corners, technical interfaces, no rounding | | XS | `rounded-xs` | 0.25x | 2px | Hairline rounding on very small marks | | SM | `rounded-sm` | 0.5x | 4px | Keyboard shortcuts, checkboxes, tight layouts | | MD | `rounded-md` | 0.75x | 6px | Compact controls, inline chips | | Default | `rounded` | 1x | 8px | The base unit itself | | LG | `rounded-lg` | 1x | 8px | Menu items nested inside a popover | | XL | `rounded-xl` | 1.5x | 12px | Inputs, selects, popovers, dropdowns, notifications | | 2XL | `rounded-2xl` | 2x | 16px | Modals, drawers | | Default (Custom) | `rounded-default` | 2.5x | 20px | Tables, skeletons, soft friendly corners | | 3XL | `rounded-3xl` | 3x | 24px | Oversized surfaces | | 4XL | `rounded-4xl` | 4x | 32px | Hero-scale surfaces | | Pill | `rounded-pill` | — | 9999px | Pills, badges, fully rounded buttons | `rounded-none` and `rounded-pill` are absolutes: they stay 0 and 9999px no matter what `--radius` is set to. ## Combining Borders and Radius Use border widths and radius together to create various UI patterns: ```gts preview ``` ## Best Practices ### Maintain Consistency Use consistent border styles for similar UI elements throughout your application: - **Forms**: Use default border width with default or small radius - **Cards**: Use thin borders with medium to large radius - **Alerts**: Use thin to heavy borders with appropriate radius for the context - **Buttons**: Use default radius or pill for interactive elements ### Visual Hierarchy Use border weights to establish hierarchy: - **Thin**: Background elements, subtle divisions - **Default**: Primary UI elements - **Heavy**: Active states, selections, focus indicators - **Aggressive**: Critical states, errors, important boundaries ### Radius and Component Size Pair border radius with component size: - **Small components** (badges, small buttons): Use `rounded` or `rounded-sm` - **Medium components** (buttons, inputs): Use `rounded` or `rounded-md` - **Large components** (cards, panels): Use `rounded-lg` or `rounded-xl` - **Extra large components** (heroes, features): Use `rounded-xl` or `rounded-2xl` ## Customization Override border widths and radius using CSS variables: ```css @import "@frontile/theme"; @theme { /* Customize border widths */ --border-width-thin: 1px; --border-width-default: 1.5px; --border-width-heavy: 3px; /* Customize border radius — one knob for the whole scale */ --radius: 0.75rem; } ``` ### Scaling the whole system Because every step is `calc(var(--radius) * n)`, one value controls how round the entire library looks. Nothing else has to change: | `--radius` | Menus / inputs | Menu items | Modals | |-----------|----------------|-----------|--------| | `0` | 0px | 0px | 0px | | `0.25rem` | 6px | 4px | 8px | | `0.5rem` (default) | 12px | 8px | 16px | | `0.75rem` | 18px | 12px | 24px | | `1rem` | 24px | 16px | 32px | The same knob is available from the JavaScript plugin config, where it can also differ per theme: ```js frontile({ layout: { radius: { DEFAULT: '0.75rem' } } }); ``` Individual steps remain overridable when you need to break one out of the proportional scale: ```css @theme { --radius: 0.5rem; --radius-2xl: 28px; /* modals rounder than the scale would give */ } ``` For more customization options, see the [Configuration Guide](../configuration/customization.md). --- # Surfaces Source: /docs/theming/design-tokens/surfaces.md # Surface System The Surface system creates depth and hierarchy in your interfaces using opaque roles and two families of translucent veils. ## Overview The Surface system consists of three types: - **Surface Roles** - Semantic, opaque surface tokens for specific UI contexts (app, canvas, card, table, modal, input) - **Surface Overlay** (subtle, soft, mild, firm, strong) - Translucent layers that push an element _into_ its background - **Surface Lift** (subtle, soft, mild, firm, strong) - Translucent layers that pull an element _up off_ its background ## Surface Roles Surface roles provide semantic meaning to your surfaces based on their context in the UI hierarchy. Instead of choosing arbitrary scale values, use these semantic tokens that automatically adapt to the active theme. ### Available Roles #### App (`bg-surface-app`) Root application background layer (hierarchy level 0). **When to use:** - App shell root - Full-viewport base layer - Root layout behind navigation ```gts preview ``` #### Canvas (`bg-surface-canvas`) Component contrast baseline (hierarchy level 1). **When to use:** - Main content areas - Page containers that need contrast from app background - Component baseline surfaces ```gts preview ``` #### Card (`bg-surface-card`) Elevated card container surface (hierarchy level 1). Opaque white in light mode; in dark mode it is a translucent veil (white @ 7%, the same value as `overlay-subtle`), so whatever sits behind it shows through faintly. Where a surface has to stay opaque, use `surface-table` — the same colour without the transparency. **When to use:** - Product cards - Article previews - Content blocks ```gts preview ``` #### Table (`bg-surface-table`) Data table surface (hierarchy level 1). Usually the same color as a card, but must be opaque. Tables need that guarantee. A sticky header, a frozen row, or a pinned column paints over the rows and columns scrolling beneath it, and anything translucent lets them show through. **When to use:** - Data table containers, and the sticky parts inside them ```gts preview ``` #### Modal (`bg-surface-modal`) Modal, drawer, and popover container surface, highest elevation (hierarchy level 3). **When to use:** - Modal dialogs - Drawers - Popovers and dropdown menus - Confirmation prompts Pair it with `bg-surface-overlay-strong dark:bg-surface-lift-strong` for the backdrop behind modals and drawers. ```gts preview ``` #### Input (`bg-surface-input`) Form control surface for inputs, checkboxes, radios, and similar controls (hierarchy level -1). **When to use:** - Text inputs - Checkboxes and radios - Any native form control background ```gts preview ``` ### Hierarchy Levels Surface roles are designed to create visual depth through a hierarchy system: | Level | Role | Purpose | | ----- | ------------------- | -------------------------------------------------------- | | -1 | Input | Recessed below canvas | | 0 | App | Root application background | | 1 | Canvas, Card, Table | Component contrast baseline and first level of elevation | | 3 | Modal | Highest elevation (modals, drawers, popovers, dropdowns) | In **light mode**, elevated surfaces appear brighter (white) against gray backgrounds following the elevation-luminance principle. In **dark mode**, elevated surfaces appear progressively lighter against near-black backgrounds. ## Surface Overlay (subtle, soft, mild, firm, strong) Translucent overlays with alpha channel that stack on top of solid surfaces. These add depth through transparency rather than changing the base color. ### When to Use - **Sections within components**: Modal footers, card headers, dropdown sections - **Cards/panels on pages**: Elevated surfaces that float on top of the page background - **Hover states**: Subtle translucent effect on hover - **Progress bars, selection highlights**: Semi-transparent UI elements - **`strong`**: The heaviest step (75% in light, 95% in dark), far beyond a hover or elevation hint. In light mode this is the backdrop behind modals and drawers; in dark mode the black scrim is `lift-strong` instead, since the families mirror each other — see [Surface Lift](#surface-lift-subtle-soft-mild-firm-strong) ### Example: Modal with Overlay Footer ```hbs {{! Modal base uses the modal surface role }} <:default> Modal content on opaque base <:footer> Footer with subtle overlay effect ``` ### Example: Card on Page ```hbs {{! Page has a solid app background }}
{{! Card floats on top with translucent overlay }}

Card Title

Card content with depth

``` ### Stacking Overlays Overlays can stack on top of each other to create progressive depth: ```hbs
Layer 1 (subtle)
Layer 2 (soft)
Layer 3 (mild)
Layer 4 (firm)
``` ### Example: Modal/Drawer Backdrop ```hbs Modal content ``` ## Surface Lift (subtle, soft, mild, firm, strong) Lift is the mirror of overlay. Overlay darkens in light mode and lightens in dark mode, pressing an element _into_ the page. Lift does the opposite — a white veil on a light page, a black veil on a dark one — so the element reads as floating _above_ whatever it covers. Both families share the same level names, and both adapt to the active theme automatically: | Level | Light mode | Dark mode | | -------- | ----------- | ----------- | | `subtle` | white @ 30% | black @ 10% | | `soft` | white @ 50% | black @ 20% | | `mild` | white @ 70% | black @ 30% | | `firm` | white @ 90% | black @ 40% | | `strong` | white @ 95% | black @ 75% | Every lift level flips with the scheme, `strong` included. The two families are exact mirrors, which has one consequence worth knowing: the heavy black veil used as a modal backdrop is `overlay-strong` in light mode but `lift-strong` in dark mode. A component that wants a dark scrim in both schemes pairs them — `bg-surface-overlay-strong dark:bg-surface-lift-strong` — which is what Overlay's backdrop does. ### When to Use - **Frosted/glass panels**: Pair with `backdrop-blur-*` for a translucent panel that stays legible over busy content - **Sticky headers and toolbars**: Content scrolls under them without washing them out - **Floating controls over imagery**: Media captions, hero overlays, map controls - **Anything that must read as _above_ the page** rather than recessed into it Reach for `surface-overlay-*` instead when the element belongs to the surface it sits on — hover states, table stripes, section fills. ### Example: Frosted Sticky Header ```hbs
``` ### Example: Caption Over an Image ```hbs
Floats above the image in either theme
``` ### Example: Overlay vs Lift The same nesting, one family each — overlay recedes, lift advances: ```hbs
{{! Recedes into the page }}
Overlay
{{! Floats above the page }}
Lift
``` ## Choosing the Right Surface Follow this decision flow: 1. **Does this match a specific UI context?** - If yes → Use `surface-{role}` (app, canvas, card, table, modal, input) - Surface roles provide semantic meaning and automatically adapt to themes. 2. **Is this the base container and no role matches?** - Use `surface-app` for most cases. 3. **Does it need to be translucent?** - If yes → Continue to next step - If no → Use a surface role 4. **Should it read as recessed into the surface, or floating above it?** - Recessed (hover states, section fills, table stripes) → Use `surface-overlay-*` - Floating (frosted panels, sticky headers, captions over media) → Use `surface-lift-*` - If it's a modal/drawer backdrop → Use `surface-overlay-strong dark:surface-lift-strong` ## Common Patterns ### Pattern 1: Page Layout with Cards ```gts preview ``` ### Pattern 2: Sidebar Layout ```gts preview ``` ### Pattern 3: Form with Inputs ```gts preview ``` ## Migrating from content1-4 If you're upgrading from an older version that used `content1-4`, here's the migration mapping: | Old | New | Reason | | ------------------------------ | ----------------------------------- | --------------------------------- | | `bg-content1` | `bg-surface-app` | Base opaque surface | | `bg-content3 dark:bg-content2` | `bg-surface-overlay-soft` | Single value works in both themes | | `selection:bg-content3` | `selection:bg-surface-overlay-soft` | Translucent selection highlight | ## Best Practices ### Do - **Prefer surface roles** (`surface-canvas`, `surface-card`, etc.) over ad hoc colors when they match your UI context - Stack overlays on a surface role for depth - Use `overlay-strong` with `dark:lift-strong` for modal/drawer backdrops - Test in both light and dark modes - Follow the hierarchy levels for visual consistency ### Don't - Don't use overlay without a solid role beneath it - Don't mix surface system with old content1-4 - Don't use too many overlay layers (max 3-4) - Don't forget to check contrast ratios --- # Elevation Source: /docs/theming/design-tokens/elevation.md # Elevation Frontile provides an elevation system using shadows to create visual depth and hierarchy in your interface. ## Overview Elevation helps establish spatial relationships and visual hierarchy by making elements appear "closer" or "further" from the user. Frontile provides six elevation levels (0-5), each designed for specific UI contexts. Higher elevations indicate elements that are more prominent or interactive, such as floating menus, modals, and tooltips. ## Elevation Levels ```gts preview collapsible ``` ## When to Use Each Level | Level | Utility | Use Cases | Examples | | ----- | -------------------- | --------------------------------- | --------------------------------------------------------- | | 0 | `shadow-elevation-0` | Flat elements, no depth needed | Inline content, flat buttons, embedded content | | 1 | `shadow-elevation-1` | Subtle separation from background | Hover states, subtle cards, list items | | 2 | `shadow-elevation-2` | Standard raised surfaces | Cards, panels, tiles, navigation bars | | 3 | `shadow-elevation-3` | Floating elements above content | Dropdowns, tooltips, popovers, date pickers | | 4 | `shadow-elevation-4` | Modal overlays | Modals, dialogs, drawers, sheets | | 5 | `shadow-elevation-5` | Maximum emphasis | Critical alerts, system notifications, top-layer elements | ## Common Use Cases ### Cards and Panels ```gts preview collapsible ``` ### Interactive States ```gts preview ``` ## Best Practices ### Establish Clear Hierarchy Use elevation consistently to indicate the z-axis position of elements: - **Base layer** (level 0-1): Page content, static elements - **Raised layer** (level 2): Cards, panels, navigation - **Floating layer** (level 3): Dropdowns, popovers, tooltips - **Overlay layer** (level 4-5): Modals, critical alerts ### Avoid Elevation Confusion Don't use high elevation for non-interactive or less important elements. Reserve levels 4-5 for elements that need to command attention. ### Transition Elevations Animate elevation changes for smooth interactions: ```html
Smooth elevation transition
``` ### Consider Dark Mode Shadows may be less visible in dark mode. Consider combining elevation with subtle borders: ```html
Enhanced depth with border
``` ## Accessibility ### Don't Rely on Shadows Alone Never use shadows as the only indicator of important information. Always provide additional visual cues: - Use borders for critical boundaries - Use semantic colors for status - Provide clear text labels - Ensure proper focus indicators ### Test in Different Lighting Shadows may be difficult to perceive: - On certain displays (high brightness, poor contrast) - For users with visual impairments - In different lighting conditions Always provide alternative visual cues beyond elevation. ## Customization Override elevation shadows using CSS variables: ```css @import '@frontile/theme'; @theme { /* Customize specific elevation levels */ --shadow-elevation-1: 0px 2px 4px rgba(0, 0, 0, 0.05); --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.1); --shadow-elevation-3: 0px 8px 24px rgba(0, 0, 0, 0.15); --shadow-elevation-4: 0px 16px 48px rgba(0, 0, 0, 0.2); } ``` ### Theme-Specific Shadows Adjust shadows for light and dark themes: ```css /* Light theme */ .light, .dark .theme-inverse { --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.1); } /* Dark theme */ .dark, .light .theme-inverse { --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.4); } ``` For more customization options, see the [Configuration Guide](../configuration/customization.md). --- # Configuration Overview Source: /docs/theming/configuration/overview.md # Configuration Learn how to configure and customize Frontile's theme system to match your application's design requirements. ## Overview Frontile provides two primary methods for configuring your theme: 1. **CSS Variables** - Recommended for most customizations 2. **JavaScript Configuration** - For complex theme setups and color customization ## CSS Variables (Recommended) The most straightforward way to customize Frontile is using CSS variables in your stylesheet. This approach uses Tailwind v4's `@theme` directive for simple, performant customization. ### Quick Start Add customizations after importing the theme in your `app/styles/app.css`: ```css title="app/styles/app.css" @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; @import "@frontile/theme"; @theme { /* Customize design tokens */ --radius: 12px; --border-width-default: 2px; --size-icon-md: 20px; --opacity-hover: .85; } ``` ### What You Can Customize Use CSS variables to customize: - **Border radius** - `--radius` (one knob; the whole `rounded-*` scale is derived from it), plus `--radius-pill` and individual steps as escape hatches - **Border widths** - `--border-width-thin`, `--border-width-heavy` - **Icon sizes** - `--size-icon-sm`, `--size-icon-md`, `--size-icon-lg` - **Shadows** - `--shadow-elevation-1`, `--shadow-elevation-2` - **Opacity** - `--opacity-hover`, `--opacity-disabled` - **Typography** - Font families, sizes, and text styles - **Component sizes** - Modal and drawer sizes ### Benefits of CSS Variables - **Simple** - No JavaScript configuration needed - **Fast** - Changes are applied at CSS level - **Flexible** - Can be changed at runtime - **Theme-specific** - Easy to customize per theme (light/dark) ### When to Use CSS Variables Choose CSS variables when you need to: - Override specific design token values - Adjust border radius, spacing, or sizing - Customize per-theme values (different in light vs dark) - Make runtime changes via JavaScript ## JavaScript Configuration For more complex customization, especially colors, use JavaScript configuration. This method provides programmatic control and type safety. ### Quick Start Create `frontile.js` in your project root: ```js title="frontile.js" const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ defaultTheme: 'light', themes: { light: { colors: { primary: { subtle: '#eff6ff', soft: '#93c5fd', DEFAULT: '#3b82f6', strong: '#1e40af' } } }, dark: { colors: { primary: { subtle: '#1e3a8a', soft: '#3b82f6', DEFAULT: '#60a5fa', strong: '#dbeafe' } } } } }); ``` Then reference it in your CSS: ```css @import 'tailwindcss' source('../../'); @plugin "./../../frontile.js"; @import "@frontile/theme"; ``` ### What You Can Customize Use JavaScript configuration for: - **Semantic colors** - Primary, success, danger, warning, neutral - **Default theme** - Which theme loads by default - **Theme variants** - Create multiple theme variations - **Complex configurations** - Programmatic theme generation ### When to Use JavaScript Configuration Choose JavaScript configuration when you need to: - Customize semantic colors (primary, success, danger, etc.) - Set the default theme (light or dark) - Create multiple theme variants - Generate themes programmatically - Share configuration across projects ## Comparison | Feature | CSS Variables | JavaScript Config | |---------|---------------|-------------------| | **Border radius** | ✅ Recommended | ❌ Not needed | | **Semantic colors** | ❌ Use JS instead | ✅ Recommended | | **Icon sizes** | ✅ Recommended | ❌ Not needed | | **Elevation shadows** | ✅ Recommended | ❌ Not needed | | **Typography** | ✅ Recommended | ❌ Not needed | | **Default theme** | ❌ Not possible | ✅ Use JS | | **Runtime changes** | ✅ Easy | ❌ Requires rebuild | | **Per-theme values** | ✅ Simple | ⚠️ More complex | ## Common Patterns ### Pattern 1: Simple Adjustments For minor adjustments to existing tokens, use CSS variables: ```css @theme { --radius: 6px; /* scales every rounded-* step down proportionally */ --opacity-hover: .9; } ``` ### Pattern 2: Custom Primary Colors For primary color customization, use JavaScript: ```js module.exports = frontile({ themes: { light: { colors: { primary: { subtle: '#f0f9ff', soft: '#7dd3fc', DEFAULT: '#0ea5e9', strong: '#0c4a6e' } } } } }); ``` ### Pattern 3: Mixed Approach Combine both methods for comprehensive customization: ```js // frontile.js - Customize colors module.exports = frontile({ themes: { light: { colors: { /* custom colors */ } } } }); ``` ```css /* app.css - Customize design tokens */ @theme { --radius: 8px; --size-icon-md: 18px; } ``` ### Pattern 4: Theme-Specific Overrides Use CSS for theme-specific token values: ```css /* Light theme */ .light, .dark .theme-inverse { --opacity-hover: .85; --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.1); } /* Dark theme */ .dark, .light .theme-inverse { --opacity-hover: .75; --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.3); } ``` ## Next Steps - [CSS Variables Reference](css-variables.md) - Complete list of available variables - [Theme Switching](theme-switching.md) - Implement light/dark mode - [Customization Guide](customization.md) - Detailed customization examples --- # CSS Variables Source: /docs/theming/configuration/css-variables.md # CSS Variables Reference Complete reference of CSS variables available in Frontile's theme system for customization. ## How CSS Variables Work Frontile uses CSS variables in two ways: ### 1. Theme Variables (`@theme` block) Variables defined in the `@theme` block automatically generate Tailwind utility classes: ```css @theme { --radius: 8px; /* Generates: rounded utility */ --radius-xl: 12px; /* Generates: rounded-xl utility */ } ``` These are the primary customization points for design tokens. ### 2. Component Variables (`:root` or theme selectors) Variables defined in `:root` or theme-specific selectors are for component-specific values: ```css :root { --modal-lg: 32rem; /* Used by Modal component */ --drawer-md: 28rem; /* Used by Drawer component */ } ``` ## Typography Variables ### Font Families ```css @theme { --font-header: system-ui, sans-serif; --font-body: system-ui, sans-serif; --font-code: 'Courier New', monospace; --font-label: system-ui, sans-serif; --font-caption: system-ui, sans-serif; --font-marquee: system-ui, sans-serif; } ``` ### Text Styles Frontile provides text style variables. Each text style category has multiple size variants: - **Marquee**: `--text-marquee-5xs` through `--text-marquee-3xl` - **Header**: `--text-header-4xs` through `--text-header-3xl` - **Strong**: `--text-strong-4xs` through `--text-strong-3xl` - **Body**: `--text-body-5xs` through `--text-body-3xl` - **Code**: `--text-code-sm`, `--text-code-md` - **Caption**: `--text-caption-sm`, `--text-caption-md` - **Label**: `--text-label-nano` through `--text-label-3xl` Each text style includes font-size, font-family, font-weight, letter-spacing, and line-height properties. See [Typography documentation](../design-tokens/typography.md) for detailed information. ## Layout Variables ### Border Width ```css @theme { --border-width-thin: 0.5px; --border-width-default: 1px; --border-width-heavy: 2px; --border-width-aggressive: 4px; } ``` **Generated utilities:** `border-thin`, `border`, `border-heavy`, `border-aggressive` ### Border Radius Every step is derived from a single `--radius` base, so you can dial the roundness of the whole library with one value: ```css @theme { --radius: 0.5rem; /* 8px — the one knob */ --radius-none: 0px; --radius-xs: calc(var(--radius) * 0.25); /* 2px */ --radius-sm: calc(var(--radius) * 0.5); /* 4px */ --radius-md: calc(var(--radius) * 0.75); /* 6px */ --radius-lg: calc(var(--radius) * 1); /* 8px */ --radius-xl: calc(var(--radius) * 1.5); /* 12px */ --radius-2xl: calc(var(--radius) * 2); /* 16px */ --radius-3xl: calc(var(--radius) * 3); /* 24px */ --radius-4xl: calc(var(--radius) * 4); /* 32px */ --radius-default: calc(var(--radius) * 2.5); /* 20px */ --radius-pill: 9999px; } ``` **Generated utilities:** `rounded-none`, `rounded-xs`, `rounded-sm`, `rounded-md`, `rounded`, `rounded-lg`, `rounded-xl`, `rounded-2xl`, `rounded-3xl`, `rounded-4xl`, `rounded-default`, `rounded-pill` To make every component softer or sharper, override `--radius` alone — the rest of the scale follows: ```css @theme { --radius: 0.75rem; /* menus 18px, list items 12px, modals 24px */ } ``` ```css @theme { --radius: 0; /* square everything off */ } ``` `--radius-none` and `--radius-pill` are absolutes and deliberately do not scale, so `rounded-full` buttons stay pills at any base value. ### Icon Sizes ```css @theme { --size-icon-pico: /* ... */; --size-icon-nano: /* ... */; --size-icon-micro: /* ... */; --size-icon-3xs: /* ... */; --size-icon-2xs: /* ... */; --size-icon-xs: /* ... */; --size-icon-sm: /* ... */; --size-icon-md: /* ... */; --size-icon-lg: /* ... */; --size-icon-xl: /* ... */; --size-icon-2xl: /* ... */; --size-icon-3xl: /* ... */; --size-icon-kilo: /* ... */; --size-icon-mega: /* ... */; } ``` **Generated utilities:** `size-icon-pico` through `size-icon-mega` See [Icon Sizes documentation](../design-tokens/icons.md) for usage guidelines. ### Elevation Shadows ```css @theme { --shadow-elevation-0: /* ... */; --shadow-elevation-1: /* ... */; --shadow-elevation-2: /* ... */; --shadow-elevation-3: /* ... */; --shadow-elevation-4: /* ... */; --shadow-elevation-5: /* ... */; } ``` **Generated utilities:** `shadow-elevation-0` through `shadow-elevation-5` See [Elevation documentation](../design-tokens/elevation.md) for usage guidelines. ### Opacity ```css @theme { --opacity-hover: .8; --opacity-disabled: .5; } ``` **Generated utilities:** `opacity-hover`, `opacity-disabled` ## Component Variables These variables are used by specific Frontile components and should be defined in `:root` or theme-specific selectors (not in `@theme` block): ### Modal Sizes ```css :root { --modal-xs: 22rem; --modal-sm: 30rem; --modal-md: 35rem; --modal-lg: 48rem; --modal-xl: 60rem; --modal-full: 100%; } ``` ### Drawer Sizes ```css :root { --drawer-xs: 22rem; --drawer-sm: 30rem; --drawer-md: 48rem; --drawer-lg: 64rem; --drawer-xl: 80rem; --drawer-full: 100%; } ``` ## Color Variables **Important:** Color variables should be customized using JavaScript configuration, not CSS variables directly. See [Color Customization](customization.md#colors) for details. Frontile's semantic colors use a system that automatically calculates: - Light and dark theme variations - Contrasting text colors (`on-{color}` classes) - Colors in OKLCH format for perceptual uniformity ## Overriding Variables ### Global Overrides Override in the `@theme` block for design tokens: ```css @import "@frontile/theme"; @theme { --radius: 12px; --border-width-default: 2px; --opacity-hover: .9; --size-icon-md: 20px; } ``` ### Theme-Specific Overrides Use theme selectors for per-theme customization: ```css /* Light theme - targets .light AND .theme-inverse within .dark */ .light, .dark .theme-inverse { --opacity-hover: .85; --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.1); } /* Dark theme - targets .dark AND .theme-inverse within .light */ .dark, .light .theme-inverse { --opacity-hover: .75; --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.3); } ``` **Why both selectors?** This ensures customizations apply to: - Regular theme usage (`.light` or `.dark`) - Theme-inverse sections (`.theme-inverse` within opposite theme) ### Component-Specific Overrides Override component variables in `:root`: ```css :root { --modal-lg: 48rem; --drawer-md: 32rem; } ``` Or per-theme: ```css .dark { --modal-lg: 56rem; } ``` ## Usage Examples ### Example 1: Customize Border Radius ```css @theme { /* Softer corners throughout the app */ --radius: 12px; --radius-xl: 20px; --radius-pill: 9999px; } ``` ### Example 2: Adjust Icon Sizes ```css @theme { /* Slightly larger icons */ --size-icon-sm: 15px; --size-icon-md: 19px; --size-icon-lg: 23px; } ``` ### Example 3: Theme-Specific Shadows ```css /* Subtler shadows in light mode */ .light, .dark .theme-inverse { --shadow-elevation-1: 0px 1px 3px rgba(0, 0, 0, 0.05); --shadow-elevation-2: 0px 2px 8px rgba(0, 0, 0, 0.08); } /* Stronger shadows in dark mode */ .dark, .light .theme-inverse { --shadow-elevation-1: 0px 2px 4px rgba(0, 0, 0, 0.3); --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.4); } ``` ### Example 4: Custom Font Families ```css @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-weight: 100 900; } @theme { --font-header: 'Inter', system-ui, sans-serif; --font-body: 'Inter', system-ui, sans-serif; --font-label: 'Inter', system-ui, sans-serif; } ``` ### Example 5: Adjust Component Sizes ```css :root { /* Larger modals for desktop */ --modal-md: 36rem; --modal-lg: 48rem; --modal-xl: 64rem; } /* Smaller on mobile */ @media (max-width: 768px) { :root { --modal-md: 90vw; --modal-lg: 90vw; } } ``` ## Best Practices ### Use @theme for Design Tokens Always use `@theme` block for variables that should generate utility classes: ```css /* ✓ Good */ @theme { --radius: 10px; } /* ✗ Avoid */ :root { --radius: 10px; /* Won't generate rounded utility */ } ``` ### Use :root for Component Values Use `:root` for component-specific values that don't need utilities: ```css /* ✓ Good */ :root { --modal-lg: 48rem; --drawer-md: 32rem; } ``` ### Keep Theme Selectors Consistent Always use both selectors for theme-specific overrides: ```css /* ✓ Good - covers all cases */ .light, .dark .theme-inverse { --opacity-hover: .85; } /* ✗ Incomplete - misses .theme-inverse */ .light { --opacity-hover: .85; } ``` ### Document Your Customizations Add comments to explain custom values: ```css @theme { /* Brand requires 16px minimum for icon clarity */ --size-icon-md: 20px; /* Softer corners match brand guidelines */ --radius: 12px; } ``` ## Runtime Customization CSS variables can be changed at runtime using JavaScript: ```js // Change globally document.documentElement.style.setProperty('--radius', '16px'); // Change for specific element element.style.setProperty('--opacity-hover', '0.9'); // Read current value const radius = getComputedStyle(document.documentElement) .getPropertyValue('--radius'); ``` This is useful for user preferences, theme builders, or dynamic customization features. ## Next Steps - [Customization Guide](customization.md) - Detailed customization examples - [Theme Switching](theme-switching.md) - Light/dark mode implementation - [Design Tokens](../design-tokens/overview.md) - Understanding design tokens --- # Theme Switching Source: /docs/theming/configuration/theme-switching.md # Theme Switching Frontile supports light and dark themes, and theme inversion for creating visual contrast within your application. ## Basic Light/Dark Mode Toggle between light and dark themes by adding or removing the `dark` class on a parent element (typically `` or ``): ```html ``` The `light` class is optional since light mode is the default. However, it's useful when you want to be explicit. ## Implementing Theme Switching ### Client-Side Toggle Here's a basic implementation. It stays a static snippet rather than a live demo because it writes to `localStorage` and swaps classes on ``, which would fight this site's own theme switcher. ```gts import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { Button } from 'frontile'; export default class ThemeToggle extends Component { @tracked isDark = false; constructor(owner: unknown, args: Record) { super(owner as never, args); // Saved choice wins; fall back to the system preference. const saved = localStorage.getItem('theme'); this.isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches; this.applyTheme(); } @action toggleTheme(): void { this.isDark = !this.isDark; this.applyTheme(); localStorage.setItem('theme', this.isDark ? 'dark' : 'light'); } applyTheme(): void { const html = document.documentElement; // Both classes are meaningful — `light` is what makes `theme-inverse` // work inside a dark region — so set one and clear the other. html.classList.toggle('dark', this.isDark); html.classList.toggle('light', !this.isDark); } } ``` Frontile's own documentation site does exactly this — see [`docfy-theme-switcher.gts`](https://github.com/josemarluedke/frontile/blob/main/site/app/components/docfy/docfy-theme-switcher.gts) for a version that also reacts to the system preference changing while the page is open, and guards against running during server-side rendering. ### Respecting System Preference Detect and respond to the user's system theme preference: ```js // Check system preference on load const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; // Listen for system preference changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { const isDark = e.matches; // Update theme document.documentElement.classList.toggle('dark', isDark); document.documentElement.classList.toggle('light', !isDark); }); ``` ### Persisting Theme Choice Store the user's theme preference: ```js // Save preference localStorage.setItem('theme', 'dark'); // Load preference const savedTheme = localStorage.getItem('theme'); if (savedTheme) { document.documentElement.classList.add(savedTheme); } // Or use system preference if no saved preference if (!savedTheme) { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; document.documentElement.classList.add(prefersDark ? 'dark' : 'light'); } ``` ## How Theme Classes Work Frontile uses CSS selectors that apply theme-specific styles: ```css /* Light theme styles apply to: */ .light *, /* Elements in light mode */ .dark .theme-inverse * /* Theme-inverse within dark mode */ /* Dark theme styles apply to: */ .dark *, /* Elements in dark mode */ .light .theme-inverse * /* Theme-inverse within light mode */ ``` This means themes properly cascade and can be nested anywhere in your application. ## Theme Inverse Create sections with inverted theme colors using the `theme-inverse` utility. In light mode, these sections display dark theme colors, and vice versa. ### Basic Usage ```gts preview import { Button } from 'frontile'; ``` ### Use Cases for Theme Inverse Theme inverse is perfect for creating visual contrast and emphasis: - **Hero sections** - Dark hero on light page, or vice versa - **Feature highlights** - Draw attention to specific sections - **Sidebars/panels** - Create visual separation - **Marketing sections** - Alternate theme for variety ### Component Examples All Frontile components work automatically with theme-inverse: ```gts preview import { Button, Chip, ProgressBar } from 'frontile'; ``` ## Important: Modals and Overlays ### Default Behavior By default, Modal and Drawer components render at the top level of the DOM using portals. This means they inherit the theme from `` or ``, **not** from `theme-inverse` sections: ```gts import { Modal, Button, toggleState } from 'frontile'; const state = toggleState(false); ``` ### Rendering in Place To make modals inherit `theme-inverse`, render them in place: ```gts import { Modal, Button, toggleState } from 'frontile'; const state = toggleState(false); ``` ## Best Practices ### Always Set Base Backgrounds Surface overlay colors are semi-transparent. Always provide a solid base background: ```html
Content
Content (might be invisible!)
``` ### Use Semantic Colors Theme-inverse works automatically with semantic colors: ```html

Heading

Body text

``` ### Test Both Themes Always test your UI in both light and dark modes: ```html ``` ### Avoid Hard-Coded Colors Don't use hard-coded Tailwind colors that don't adapt to themes: ```html
Content
Content
``` ## Dark/Light Variants Tailwind's `dark:` and `light:` variants work correctly with `theme-inverse`: ```html
Content
``` The variants understand theme-inverse contexts: - `dark:` applies in dark mode AND in `.light .theme-inverse` - `light:` applies in light mode AND in `.dark .theme-inverse` ## How Theme Inverse Works Under the hood: - Theme-inverse only works with base themes (light and dark) - In light mode, `.light .theme-inverse` applies dark theme colors - In dark mode, `.dark .theme-inverse` applies light theme colors - All CSS variables are automatically swapped - Components work without modification ## Advanced: Multiple Theme Levels You can nest theme-inverse multiple times, though it's rarely needed: ```html
``` ## Next Steps - [CSS Variables Reference](css-variables.md) - Customize theme tokens - [Customization Guide](customization.md) - Theme customization examples - [Colors Documentation](../design-tokens/colors.md) - Understanding semantic colors --- # Customization Source: /docs/theming/configuration/customization.md # Customization Guide Complete guide to customizing Frontile's theme system, from simple token adjustments to comprehensive brand theming. ## Quick Start The fastest way to customize Frontile is using CSS variables in your `app/styles/app.css`: ```css title="app/styles/app.css" @import 'tailwindcss' source('../../'); @plugin "@frontile/theme/plugin/default"; @import '@frontile/theme'; @theme { --radius: 12px; --size-icon-md: 20px; --opacity-hover: 0.9; } ``` ## Customizing Colors ### Using JavaScript Configuration Colors should be customized using JavaScript configuration for best results. Create `frontile.js` in your project root: ```js title="frontile.js" collapsible const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ themes: { light: { colors: { primary: { subtle: '#f0f9ff', soft: '#7dd3fc', DEFAULT: '#0ea5e9', strong: '#0c4a6e' }, success: { subtle: '#f0fdf4', soft: '#86efac', DEFAULT: '#22c55e', strong: '#14532d' }, danger: { subtle: '#fef2f2', soft: '#fca5a5', DEFAULT: '#ef4444', strong: '#7f1d1d' }, warning: { subtle: '#fffbeb', soft: '#fde68a', DEFAULT: '#f59e0b', strong: '#78350f' } } }, dark: { colors: { primary: { subtle: '#0c4a6e', soft: '#0ea5e9', DEFAULT: '#38bdf8', strong: '#e0f2fe' } // ... other colors } } } }); ``` Then reference it in your CSS: ```css @import 'tailwindcss' source('../../'); @plugin "./../../frontile.js"; @import '@frontile/theme'; ``` ### How Color Customization Works Frontile automatically: - Generates contrasting text colors (`on-{color}-{level}`) - Converts your colors to OKLCH format for perceptual uniformity - Accepts any color format as input (hex, rgb, hsl, oklch, CSS variables) - Creates theme-aware CSS variables - Ensures proper contrast ratios ### Customizing On-Colors By default, Frontile auto-generates optimal `on-{color}-{level}` text colors (black or white) based on WCAG contrast calculations. If you need specific on-colors for brand consistency, you can override them in your theme configuration: ```js const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ themes: { light: { colors: { primary: { subtle: '#eff6ff', soft: '#93c5fd', DEFAULT: '#3b82f6', strong: '#1e40af' }, // Override specific on-colors 'on-primary': { DEFAULT: '#ffffff', // Force white text on the bare primary fill strong: '#e0f2fe' // Use light blue instead of white } // on-primary-subtle and on-primary-soft will still be auto-generated } } } }); ``` **Key behavior:** - Partial overrides work — define only the levels you want to customize, and the rest will be auto-generated - Works for: `on-neutral`, `on-primary`, `on-secondary`, `on-tertiary`, `on-success`, `on-warning`, `on-danger`, `on-surface-modal` - CSS variable references (e.g., `var(--my-color)`) are passed through as-is — auto-generation is skipped since contrast can't be calculated ## Customizing Typography ### Font Families Override font families for different text categories: ```css @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-weight: 100 900; font-display: swap; } @font-face { font-family: 'JetBrains Mono'; src: url('/fonts/jetbrains-mono.woff2') format('woff2'); font-display: swap; } @theme { --font-header: 'Inter', system-ui, sans-serif; --font-body: 'Inter', system-ui, sans-serif; --font-label: 'Inter', system-ui, sans-serif; --font-code: 'JetBrains Mono', monospace; } ``` ### Theme-Specific Fonts Use different fonts for light and dark themes: ```css .light, .dark .theme-inverse { --font-body: 'Inter', system-ui, sans-serif; } .dark, .light .theme-inverse { --font-body: 'Geist', system-ui, sans-serif; } ``` ## Customizing Border Radius ### Global Radius Adjust border radius throughout your application: ```css @theme { /* Softer, more rounded design */ --radius: 12px; --radius-xl: 20px; --radius-2xl: 28px; --radius-pill: 9999px; } ``` ### Sharp Design Create a more angular design: ```css @theme { /* Sharp, geometric design */ --radius: 2px; --radius-sm: 1px; --radius-md: 3px; --radius-xl: 6px; --radius-2xl: 8px; } ``` ## Customizing Icon Sizes Adjust icon sizes to match your design: ```css @theme { /* Slightly larger icons */ --size-icon-xs: 13px; --size-icon-sm: 15px; --size-icon-md: 19px; --size-icon-lg: 23px; --size-icon-xl: 27px; } ``` ## Customizing Borders ### Border Widths Adjust border thickness: ```css @theme { /* Thicker borders throughout */ --border-width-thin: 1px; --border-width-default: 2px; --border-width-heavy: 3px; --border-width-aggressive: 5px; } ``` ## Customizing Elevation ### Shadow Styles Adjust shadow intensity and style: ```css @theme { /* Subtler shadows */ --shadow-elevation-1: 0px 1px 2px rgba(0, 0, 0, 0.04); --shadow-elevation-2: 0px 2px 8px rgba(0, 0, 0, 0.06); --shadow-elevation-3: 0px 8px 16px rgba(0, 0, 0, 0.08); --shadow-elevation-4: 0px 16px 32px rgba(0, 0, 0, 0.12); } ``` ### Theme-Specific Shadows Different shadows for light and dark modes: ```css /* Light theme - softer shadows */ .light, .dark .theme-inverse { --shadow-elevation-2: 0px 2px 8px rgba(0, 0, 0, 0.08); --shadow-elevation-3: 0px 8px 20px rgba(0, 0, 0, 0.12); } /* Dark theme - stronger shadows for contrast */ .dark, .light .theme-inverse { --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.4); --shadow-elevation-3: 0px 12px 28px rgba(0, 0, 0, 0.5); } ``` ## Customizing Opacity Adjust hover and disabled opacity: ```css @theme { --opacity-hover: 0.9; --opacity-disabled: 0.4; } ``` ## Customizing Component Sizes ### Modal Sizes Adjust modal dimensions: ```css :root { --modal-xs: 18rem; --modal-sm: 22rem; --modal-md: 32rem; --modal-lg: 48rem; --modal-xl: 64rem; } /* Responsive sizing */ @media (max-width: 768px) { :root { --modal-md: 90vw; --modal-lg: 90vw; --modal-xl: 95vw; } } ``` ### Drawer Sizes ```css :root { --drawer-sm: 20rem; --drawer-md: 32rem; --drawer-lg: 48rem; --drawer-xl: 64rem; } ``` ## Setting Default Theme Specify which theme loads by default: ```js // frontile.js module.exports = frontile({ defaultTheme: 'dark', // or 'light' themes: { light: {/* ... */}, dark: {/* ... */} } }); ``` ## Complete Brand Example Here's a complete example of customizing Frontile for a brand: ### frontile.js ```js const { frontile } = require('@frontile/theme/plugin'); module.exports = frontile({ defaultTheme: 'light', themes: { light: { colors: { // Purple brand color primary: { subtle: '#faf5ff', soft: '#c084fc', DEFAULT: '#9333ea', strong: '#581c87' }, // Success remains green success: { subtle: '#f0fdf4', soft: '#86efac', DEFAULT: '#22c55e', strong: '#14532d' }, // Custom orange warning warning: { subtle: '#fff7ed', soft: '#fdba74', DEFAULT: '#f97316', strong: '#7c2d12' } } }, dark: { colors: { primary: { subtle: '#581c87', soft: '#9333ea', DEFAULT: '#a855f7', strong: '#f3e8ff' } // ... other colors } } } }); ``` ### app/styles/app.css ```css @import 'tailwindcss' source('../../'); @plugin "./../../frontile.js"; @import '@frontile/theme'; /* Load custom fonts */ @font-face { font-family: 'Poppins'; src: url('/fonts/poppins-var.woff2') format('woff2'); font-weight: 100 900; font-display: swap; } @theme { /* Typography */ --font-header: 'Poppins', system-ui, sans-serif; --font-body: 'Poppins', system-ui, sans-serif; --font-label: 'Poppins', system-ui, sans-serif; /* Rounded design */ --radius: 12px; --radius-xl: 20px; --radius-2xl: 24px; /* Slightly larger icons */ --size-icon-sm: 16px; --size-icon-md: 20px; --size-icon-lg: 24px; /* Subtle opacity changes */ --opacity-hover: 0.9; --opacity-disabled: 0.5; } /* Component customization */ :root { /* Wider modals */ --modal-md: 36rem; --modal-lg: 52rem; } ``` ## Common Customization Patterns ### Pattern 1: Minimal/Sharp Design ```css @theme { --radius: 2px; --radius-xl: 4px; --radius-2xl: 6px; --border-width-default: 1px; --shadow-elevation-2: 0px 1px 3px rgba(0, 0, 0, 0.06); } ``` ### Pattern 2: Soft/Rounded Design ```css @theme { --radius: 16px; --radius-xl: 24px; --radius-2xl: 32px; --shadow-elevation-2: 0px 8px 24px rgba(0, 0, 0, 0.08); } ``` ### Pattern 3: Bold/High Contrast ```css @theme { --border-width-default: 2px; --border-width-heavy: 3px; --opacity-hover: 0.95; } .light, .dark .theme-inverse { --shadow-elevation-2: 0px 4px 12px rgba(0, 0, 0, 0.15); } ``` ### Pattern 4: Subtle/Minimal ```css @theme { --border-width-thin: 0.5px; --border-width-default: 1px; --opacity-hover: 0.85; --shadow-elevation-2: 0px 2px 4px rgba(0, 0, 0, 0.04); } ``` ## Advanced: Per-Component Customization Override styles for specific components using Tailwind variants: ```css /* Larger buttons */ .btn-lg { @apply px-6 py-3 text-label-lg; } /* Card with custom shadow */ .card { @apply rounded-2xl shadow-elevation-3; } /* Custom modal backdrop */ .modal-backdrop { @apply bg-surface-overlay-strong dark:bg-surface-lift-strong backdrop-blur-md; } ``` ## Testing Your Customizations ### Visual Regression Testing Test your theme in different scenarios: ```html
``` ### Browser DevTools Use browser DevTools to inspect CSS variables: ```js // Check current value getComputedStyle(document.documentElement).getPropertyValue('--radius'); // Test changes document.documentElement.style.setProperty('--radius', '16px'); ``` ## Best Practices ### Document Your Customizations Add comments explaining why values were chosen: ```css @theme { /* Brand guideline requires 16px minimum border radius */ --radius: 16px; /* Larger icons for better visibility on mobile */ --size-icon-md: 22px; } ``` ### Use Consistent Scales Maintain proportional relationships when customizing: ```css /* Good - maintains scale relationships */ @theme { --radius-sm: 4px; --radius: 8px; --radius-xl: 16px; --radius-2xl: 24px; } /* Avoid - breaks scale relationships */ @theme { --radius-sm: 2px; --radius: 20px; --radius-xl: 15px; --radius-2xl: 8px; } ``` ### Test Accessibility Ensure customizations maintain accessibility: - Maintain sufficient color contrast (WCAG AA minimum 4.5:1) - Don't reduce opacity below readable levels - Test with screen readers - Verify keyboard navigation remains clear ### Performance Considerations - CSS variable changes are performant - Avoid excessive theme switching - Use `prefers-reduced-motion` for users who need it: ```css @media (prefers-reduced-motion: reduce) { * { transition-duration: 0.01ms !important; } } ``` ## Troubleshooting ### Colors Not Updating **Problem**: Changed color values but UI doesn't update **Solution**: Colors must be configured via JavaScript, not CSS variables: ```js // Correct module.exports = frontile({ themes: { light: { colors: { primary: {/* ... */} } } } }); ``` ### Utilities Not Generated **Problem**: Custom CSS variable doesn't generate utility class **Solution**: Make sure the variable is in the `@theme` block: ```css /* Generates utilities */ @theme { --radius-custom: 10px; } /* Does NOT generate utilities */ :root { --radius-custom: 10px; } ``` ### Theme-Specific Values Not Applying **Problem**: Theme-specific overrides don't work **Solution**: Use both selectors for proper theme-inverse support: ```css /* Correct */ .light, .dark .theme-inverse { --opacity-hover: 0.85; } /* Incomplete */ .light { --opacity-hover: 0.85; } ``` ## Next Steps - [CSS Variables Reference](css-variables.md) - Complete variable list - [Design Tokens](../design-tokens/overview.md) - Understanding tokens - [Theme Switching](theme-switching.md) - Implement theme toggling