DSAI styling system reference, Bootstrap 5 utilities, CSS custom properties (design tokens), cn() utility, dark mode, and token pipeline. Replaces the old tailwindcss skill. Use this skill when generating or modifying styles, classNames, or theme configuration in DSAI consumer applications.
From hugin-v0npx claudepluginhub michelve/hugin-marketplace --plugin hugin-v0This skill uses the workspace's default tool permissions.
Provides checklists to review code for functionality, quality, security, performance, tests, and maintainability. Use for PRs, audits, team standards, and developer training.
Guides browser automation with Playwright, Puppeteer, Selenium for e2e testing and scraping. Teaches reliable selectors, auto-waits, isolation to fix flaky tests.
Enforces A/B test setup with gates for hypothesis locking, metrics definition, sample size calculation, assumptions checks, and execution readiness before implementation.
DSAI uses Bootstrap 5 as the base CSS framework, with design tokens (CSS custom properties) providing the customization layer.
NO Tailwind CSS. Never use Tailwind utility classes in DSAI projects.
DTCG JSON tokens (src/collections/) are compiled via dsai tokens build into multiple output formats:
| Output File | Purpose |
|---|---|
src/generated/css/tokens.css | Light theme tokens (:root selector, --dsai-* prefix) |
src/generated/css/tokens-dark.css | Dark theme tokens ([data-dsai-theme="dark"] selector) |
src/generated/css/dsai-theme-bs.css | Bootstrap compiled with DSAI token overrides |
src/generated/scss/_variables.scss | Sass variables for build-time usage |
src/generated/js/tokens.js | JavaScript token module |
src/generated/ts/tokens.d.ts | TypeScript type declarations |
src/generated/json/tokens.json | Raw JSON token map |
After editing any token JSON in src/collections/, run:
dsai tokens build
CRITICAL — imports in main.tsx must follow this exact order:
import '../generated/css/dsai-theme-bs.css'; // 1. Bootstrap + DSAI token overrides
import '../generated/css/tokens.css'; // 2. Light theme CSS custom properties
import '../generated/css/tokens-dark.css'; // 3. Dark theme CSS custom properties
import './index.css'; // 4. App base styles
import './custom.css'; // 5. App overrides
Changing this order will break theme resolution and specificity.
All design tokens are exposed as CSS custom properties prefixed with --dsai-:
var(--dsai-color-primary)
var(--dsai-spacing-3)
var(--dsai-font-size-base)
var(--dsai-shadow-lg)
var(--dsai-border-radius)
Use these for any value not covered by a Bootstrap utility class.
Use Bootstrap utilities — not Tailwind classes.
| Category | Classes |
|---|---|
| Display | d-flex, d-grid, d-block, d-none, d-inline |
| Flex direction | flex-row, flex-column, flex-wrap |
| Flex alignment | justify-content-center, align-items-center |
| Gap | gap-1 through gap-5 |
| Position | position-relative, position-absolute, position-fixed, position-sticky |
Margin: m-0 through m-5, directional: mt-, mb-, ms-, me-, mx-, my-
Padding: p-0 through p-5, directional: pt-, pb-, ps-, pe-, px-, py-
| Category | Classes |
|---|---|
| Alignment | text-center, text-start, text-end |
| Color | text-body-secondary, text-muted |
| Weight | fw-bold, fw-semibold |
| Size | fs-1 through fs-6 |
w-100, w-75, w-50, w-25, h-100, min-vh-100
visually-hidden (NOT sr-only), overflow-hidden
border, border-0, rounded, rounded-circle
bg-primary, bg-secondary, bg-body-secondary, bg-body-tertiary
text-primary, text-secondary, text-success, text-danger, text-warning, text-info
cn() is a simple filter-and-join helper. It is not tailwind-merge and not clsx.
function cn(...classes: (string | boolean | undefined | null)[]): string {
return classes.filter(Boolean).join(' ');
}
cn('btn', `btn-${variant}`, size === 'lg' && 'btn-lg', disabled && 'disabled', className)
Important: cn() performs no conflict resolution — it only concatenates truthy values. Class order matters for CSS specificity.
Combine cn(), Bootstrap utilities, and --dsai-* tokens in component TSX:
import { forwardRef } from 'react';
import { cn } from '@/utils/cn';
import type { AlertProps } from './Alert.types';
export const Alert = forwardRef<HTMLDivElement, AlertProps>(
function Alert({ variant = 'info', dismissible, className, children, ...props }, ref) {
return (
<div
ref={ref}
className={cn(
'alert', // Bootstrap base class
`alert-${variant}`, // Bootstrap variant
dismissible && 'alert-dismissible fade show',
className // Consumer overrides
)}
role="alert"
style={{
borderRadius: 'var(--dsai-border-radius)', // DSAI token
padding: 'var(--dsai-spacing-3) var(--dsai-spacing-4)', // DSAI tokens
}}
{...props}
>
{children}
</div>
);
}
);
Alert.displayName = 'Alert';
| Need | Use | Example |
|---|---|---|
| Layout, spacing, display | Bootstrap utility classes | d-flex gap-3 p-3 |
| Component variants | Bootstrap component classes via cn() | cn('btn', \btn-${variant}`)` |
| Brand colors, custom sizes, shadows | --dsai-* CSS custom properties | var(--dsai-color-primary) |
| Conditional classes | cn() with boolean expressions | cn('btn', loading && 'disabled') |
| Theme-aware values | Tokens (auto-switch light/dark) | var(--dsai-bg-surface) |
Rule of thumb: Use Bootstrap utilities for standard layout. Use --dsai-* tokens for brand/theme values that Bootstrap doesn't cover.
import { useDarkMode } from '@/hooks/useDarkMode';
function ThemeToggle() {
const { isDark, toggle } = useDarkMode();
return (
<Button
variant={isDark ? 'light' : 'dark'}
onClick={toggle}
aria-label={`Switch to ${isDark ? 'light' : 'dark'} mode`}
>
{isDark ? 'light' : 'dark'}
</Button>
);
}
Tokens automatically resolve to the correct value based on the active theme:
/* No conditional logic needed — tokens handle it */
.custom-card {
background: var(--dsai-bg-surface); /* white in light, dark gray in dark */
color: var(--dsai-color-body); /* dark text in light, light text in dark */
border: 1px solid var(--dsai-border-color); /* adapts per theme */
box-shadow: var(--dsai-shadow-sm); /* adapts per theme */
}
Both data attributes must be set on <html> for theming to work:
document.documentElement.setAttribute('data-dsai-theme', 'dark');
document.documentElement.setAttribute('data-bs-theme', 'dark');
| Theme | Selector |
|---|---|
| Light (default) | :root |
| Dark | [data-dsai-theme="dark"] |
| Auto-detect | prefers-color-scheme: dark media query |
The token build pipeline replaces data-bs-theme with data-dsai-theme in generated CSS files. DSAI controls theming independently while Bootstrap continues to work.
Token JSON files in src/collections/ follow the Design Token Community Group format:
{
"color": {
"blue": {
"500": {
"$value": "#0a58ca",
"$type": "color",
"$description": "Base primary brand color",
"$extensions": {
"platform": {
"scssVariableName": "$color-blue-500",
"bootstrapVersion": "5.3"
}
}
}
}
}
}
| Collection | Contents |
|---|---|
color/ | primitive, semantic, background, component, neutral, opacity (all with -dark variants) |
typography/ | sizes, weights, line heights, letter spacing, headings, display |
spacing/ | 0–11 scale: 0px, 4px, 8px, 16px, 24px, 48px, 64px, 80px, 96px, 128px, 160px |
border/ | width, radius |
shadow/ | sm, default, lg |
layout/ | container, grid breakpoints |
Primitive colors: blue, indigo, purple, pink, red, orange, yellow, green, teal, cyan — each with shades 50–950 (13 steps).
Semantic colors: primary, secondary, success, danger, warning, info, light, dark.
import { defineConfig } from '@dsai-io/tools';
export default defineConfig({
tokens: {
source: 'theme',
sourceDir: './src/figma-exports',
collectionsDir: './src',
outputDir: './src/generated',
prefix: '--dsai-',
formats: ['css', 'scss', 'js', 'ts', 'json'],
scss: {
themeEntry: 'src/scss/dsai-theme-bs.scss',
framework: 'bootstrap',
},
themes: {
enabled: true,
default: 'light',
definitions: {
light: { isDefault: true, selector: ':root' },
dark: {
selector: '[data-dsai-theme="dark"]',
mediaQuery: '(prefers-color-scheme: dark)',
},
},
},
aliases: {
importAlias: '@/',
ui: 'src/client/components/ui',
hooks: 'src/client/hooks',
utils: 'src/client/lib/utils',
},
},
});
flex, no Tailwind-style gap-3, no bg-blue-500).d-flex, gap-3, bg-primary, text-body-secondary).var(--dsai-*) custom properties or Bootstrap semantic classes.main.tsx must be maintained exactly as documented.data-dsai-theme and data-bs-theme attributes are required for theme switching.dsai tokens build to regenerate outputs.visually-hidden (Bootstrap), not sr-only (Tailwind).cn() is simple — no conflict resolution, just concatenation. Class order matters for specificity.