UNPKG

lincd

Version:

LINCD is a JavaScript library for building user interfaces with linked data (also known as 'structured data', or RDF)

408 lines (316 loc) 10.8 kB
# LINCD Styling & Theme System ## Overview The LINCD theme system is built on **Tailwind CSS v4** with a three-layer architecture that provides flexibility for different user types while maintaining consistency and reusability across the ecosystem. ## Three-Layer Architecture 1. **Tailwind Core** Base CSS variables and utilities (`--color-*`, `--shadow-*`, `--radius-*`, etc.) 2. **LINCD Defaults** Semantic variables and component utilities (`--bg-card`, `--spacing-modal`, `card-base`, etc.) 3. **App Theme** App-specific brand colors and customizations This architecture allows components to be truly reusable while giving each app complete control over its visual identity. --- <!-- See CN-specific personas in docs/cn-components-and-styling.md --> ## Architecture & Integration ### Tailwind v4 CSS-First Configuration LINCD uses Tailwind v4's new CSS-first approach. Theme configuration happens directly in CSS files using the `@theme` directive: ```css @theme { --color-primary-500: oklch(68% 0.1 250); --shadow-card: var(--shadow-md); --radius-modal: var(--radius-2xl); } ``` **What this generates:** - Color utilities: `bg-primary-500`, `text-primary-500`, `border-primary-500`, etc. - Shadow utilities: `shadow-card` - Radius utilities: `rounded-modal` ### LINCD's Semantic Variable Layer While Tailwind provides low-level variables, LINCD adds semantic meaning: **In `lincd/theme-defaults.css`:** ```css :root { /* Semantic backgrounds - describe WHAT, not HOW */ --bg-page: var(--color-gray-50); --bg-section: var(--color-gray-100); --bg-card: var(--color-white); --bg-modal: var(--color-white); /* Semantic spacing - per component type */ --spacing-card: calc(var(--spacing) * 4); --spacing-modal: calc(var(--spacing) * 6); --spacing-element-x: calc(var(--spacing) * 4); --spacing-element-y: calc(var(--spacing) * 2); /* Z-index layers */ --z-surface: 10; --z-popover: 30; --z-overlay: 40; --z-modal: 50; --z-tooltip: 100; } ``` These are then exposed as Tailwind utilities using `@utility`: ```css @utility bg-card { background-color: var(--bg-card); } @utility p-card { padding: var(--spacing-card); } @utility z-modal { z-index: var(--z-modal); } ``` ### Component Base Utilities LINCD provides component "mixins" in `lincd/utilities.css` that combine multiple properties: ```css @utility card-base { background: var(--bg-card); border-radius: var(--radius-card); box-shadow: var(--shadow-card); padding: var(--spacing-card); } @utility modal-base { background: var(--bg-modal); border-radius: var(--radius-modal); padding: var(--spacing-modal); box-shadow: var(--shadow-modal); z-index: var(--z-modal); } ``` These can be used with `@apply` in CSS Modules (see Component Creators section). --- ## For Component Creators (Package Development) ### Using Component Utilities Import utilities with `@reference` to use them with `@apply`: ```css /* MyCard.module.css */ @reference "lincd/utilities.css"; .card { @apply card-base; /* Additional custom styles */ border: 1px solid var(--color-gray-200); } ``` **What `card-base` provides:** - `background: var(--bg-card)` - `border-radius: var(--radius-card)` - `box-shadow: var(--shadow-card)` - `padding: var(--spacing-card)` ### Using CSS Variables Directly You can also use LINCD's semantic variables directly: ```css /* MyModal.module.css */ .modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--bg-modal); border-radius: var(--radius-modal); padding: var(--spacing-modal); box-shadow: var(--shadow-modal); z-index: var(--z-modal); } ``` ### Mixing Utilities and Custom Styles ```css @reference "lincd/utilities.css"; .popover { @apply popover-base; /* Override specific properties */ max-width: 300px; animation: fadeIn 0.2s ease-out; } .popoverArrow { /* Use variables for consistency */ background: var(--bg-popover); border-radius: var(--radius-sm); } ``` ### Component-Specific Variables (Optional Pattern) For frequently reused components that need customization, you can define component-specific variables with fallbacks: ```css .customCard { background: var(--my-card-bg, var(--bg-card)); border-radius: var(--my-card-radius, var(--radius-card)); padding: var(--my-card-padding, var(--spacing-card)); } ``` Apps can then override these in their theme: ```css /* App theme.css */ :root { --my-card-bg: var(--color-primary-50); --my-card-radius: var(--radius-xl); } ``` **Note:** This pattern is available but not emphasized. Most components should use LINCD's semantic variables directly. Component-specific variables are only needed for highly customizable components used across many apps. --- ## For Developers Building Apps ### Using Tailwind Utilities Directly While CN generates CSS classes, developers can use Tailwind utility classes directly in their code: ```tsx export function DeveloperCard() { return ( <div className="bg-card p-card rounded-card shadow-card border border-gray-200"> <h2 className="text-xl font-semibold text-gray-900">Title</h2> <p className="text-gray-600">Description</p> </div> ); } ``` ### Override Levels There are four practical places to customize styling, from broadest to most specific: 1. **Design tokens + semantics (Tailwind `@theme` + `:root`)** - Define tokens (`--color-*`, `--radius-*`, `--shadow-*`) and semantic variables (`--bg-section`, `--spacing-card`, `--z-modal`). - Tokens auto-generate utilities; semantics are plain CSS variables you control. ```css @theme { --color-primary-500: var(--color-teal-500); --radius-card: var(--radius-xl); --shadow-modal: var(--shadow-2xl); } :root { --bg-section: var(--color-primary-50); --spacing-card: calc(var(--spacing) * 5); } ``` 2. **Component styles (ownership-aware)** - If you own the component: edit its CSS Module directly (use `@reference` + `@apply` or variables). - If you do NOT own it: create a wrapper component and override styles there. ```css /* Own the component */ @reference "lincd/utilities.css"; .card { @apply card-base; } .card--emphasis { box-shadow: var(--shadow-lg); } /* Wrapper component overrides */ .wrappedCard :global(.card) { background: var(--color-white); } ``` 3. **Instance-level overrides** - Pass a class for a specific usage. Prefer variables over hard-coded values. ```tsx import styles from './Some.module.css'; <Card className={styles.card} /> ``` 4. **Inline styles (last resort)** - Highest specificity; use sparingly for dynamic one-offs. Note: Component-specific variables are possible (e.g., `--my-card-radius` with fallbacks) but are intentionally de-emphasized to avoid fragmentation. Prefer global tokens and semantic variables. ### Checking Out from CN Developers can take over a CN-generated codebase and continue development: - All code is readable TypeScript/CSS - CSS uses standard variables and classes - Tailwind utilities are available for new code - Can refactor CN-generated CSS to use Tailwind utilities if desired --- ## Theme Override Hierarchy Understanding the cascade (later definitions override earlier ones): 1. **Tailwind Core Defaults** `--color-gray-500`, `--shadow-md`, etc. 2. **LINCD Theme Defaults** (`lincd/theme-defaults.css`) Default primary color, semantic variables 3. **App Theme** (`src/scss/theme.css`) App-specific brand colors and customizations 4. **Component CSS Modules** Component-specific styles 5. **CN-Generated Instance Classes** Individual instance overrides 6. **Inline Styles** Ultimate override (use sparingly) ### Example of Cascade in Action ```css /* 1. Tailwind Core */ --color-sky-500: oklch(67% 0.15 235); /* 2. LINCD Defaults */ --color-primary-500: var(--color-sky-500); --bg-card: var(--color-white); --spacing-card: calc(var(--spacing) * 4); /* 3. App Theme */ --color-primary-500: var(--color-teal-500); /* Override primary */ --bg-card: var(--color-gray-50); /* Tint all cards */ /* 4. Component Module */ .productCard { background: var(--bg-card); /* Uses overridden value */ padding: var(--spacing-card); } /* 5. Instance Override (CN-generated) */ .heroCard { composes: productCard; background: var(--color-white); /* This specific instance is white */ padding: calc(var(--spacing-card) * 1.5); /* Larger padding */ } ``` --- ## Practical Examples ### Example 1: Creating a Card Component **MyCard.module.css:** ```css @reference "lincd/utilities.css"; .card { @apply card-base; border: 1px solid var(--color-gray-200); transition: box-shadow 0.2s; } .card:hover { box-shadow: var(--shadow-lg); } .cardTitle { font-size: 1.25rem; font-weight: 600; color: var(--color-gray-900); margin-bottom: 0.5rem; } .cardContent { color: var(--color-gray-600); } ``` **MyCard.tsx:** ```tsx import styles from './MyCard.module.css'; export function MyCard({ title, children }) { return ( <div className={styles.card}> <h3 className={styles.cardTitle}>{title}</h3> <div className={styles.cardContent}>{children}</div> </div> ); } ``` ### Example 2: Overriding Primary Color in App Theme **src/scss/theme.css:** ```css @import 'lincd/theme-defaults.css'; @import 'lincd/utilities.css'; @theme { /* Set app brand color */ --color-primary-50: var(--color-teal-50); --color-primary-100: var(--color-teal-100); --color-primary-200: var(--color-teal-200); --color-primary-300: var(--color-teal-300); --color-primary-400: var(--color-teal-400); --color-primary-500: var(--color-teal-500); --color-primary-600: var(--color-teal-600); --color-primary-700: var(--color-teal-700); --color-primary-800: var(--color-teal-800); --color-primary-900: var(--color-teal-900); } :root { /* Optional: Override semantic variables */ --bg-section: var(--color-primary-50); } ``` Now all components using `bg-primary-500` or `--bg-section` will reflect these changes. ### Example 4: Component with Fallback Pattern **CustomButton.module.css:** ```css .button { /* Component-specific variables with semantic fallbacks */ background: var(--button-bg, var(--color-primary-500)); color: var(--button-text, var(--color-white)); padding: var(--button-padding, var(--spacing-element-y) var(--spacing-element-x)); border-radius: var(--button-radius, var(--radius-element)); border: none; cursor: pointer; transition: background 0.2s; } .button:hover { background: var(--button-bg-hover, var(--color-primary-600)); } ``` Apps can customize this button globally: ```css :root { --button-bg: var(--color-purple-500); --button-radius: var(--radius-full); } ```