
React Impl Styling
- 12 installs
- 6 repo stars
- Updated July 8, 2026
- openaec-foundation/react-claude-skill-package
Helps with frontend development tasks.
About
react-impl-styling is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-impl-styling
- Frontend Development
- AI-coding skill
React Impl Styling by the numbers
- 12 all-time installs (skills.sh)
- Ranked #1,643 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-impl-stylingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/react-claude-skill-package ↗ |
What it does
Helps with frontend development tasks.
Files
react-impl-styling
Quick Reference
Styling Approach Decision Tree
Need to style a React component?
├── Is it a dynamic value computed at runtime (e.g., position, color from props)?
│ └── YES → Use inline styles with React.CSSProperties
├── Is the project using Tailwind CSS?
│ └── YES → Use Tailwind utility classes with cn() helper
├── Do you need scoped, file-level styles?
│ └── YES → Use CSS Modules (.module.css)
├── Do you need a component library with runtime theming?
│ └── YES → Consider CSS-in-JS (styled-components / Emotion)
└── DEFAULT → Use CSS Modules (safest, zero-runtime, best performance)Approach Comparison
| Approach | Runtime Cost | Scoping | TypeScript Support | Recommendation |
|---|---|---|---|---|
| CSS Modules | None | Automatic | Via declarations | Primary |
| Tailwind CSS | None | Via utilities | Via cn() typing | Strong alternative |
| Inline styles | Minimal | Inline | Native | Dynamic values only |
| CSS-in-JS | Moderate | Automatic | Native | Only when needed |
| Global CSS | None | None (global) | None | Variables + resets only |
Critical Warnings
NEVER use inline styles for pseudo-classes (:hover, :focus), media queries, or animations -- inline styles cannot express these. ALWAYS use CSS Modules or Tailwind for interactive/responsive styles.
NEVER use string interpolation to build className strings -- ALWAYS use clsx() or cn() for conditional class composition to avoid whitespace bugs and improve readability.
NEVER import .css files in component files without the .module.css suffix when you need scoping -- plain .css imports are global and WILL cause style collisions across components.
NEVER use CSS-in-JS (styled-components, Emotion) in React Server Components -- these libraries require a client-side runtime. ALWAYS use CSS Modules or Tailwind for RSC.
ALWAYS define a TypeScript module declaration for .module.css files to prevent import errors and enable autocomplete.
---
CSS Modules (Primary Recommendation)
CSS Modules provide automatic scoping with zero runtime cost. The bundler (Vite, webpack) transforms class names to unique hashes at build time.
Setup
Create a TypeScript declaration so imports are typed:
// src/types/css.d.ts
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export default classes;
}Basic Usage
// Button.module.css
.button {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-weight: 600;
cursor: pointer;
}
.primary {
background-color: var(--color-primary);
color: white;
}
.secondary {
background-color: transparent;
border: 1px solid var(--color-primary);
color: var(--color-primary);
}// Button.tsx
import styles from "./Button.module.css";
import { clsx } from "clsx";
interface ButtonProps {
variant?: "primary" | "secondary";
children: React.ReactNode;
}
export function Button({ variant = "primary", children }: ButtonProps) {
return (
<button className={clsx(styles.button, styles[variant])}>
{children}
</button>
);
}Composition with composes
/* Card.module.css */
.base {
border-radius: 0.5rem;
padding: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.elevated {
composes: base;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.interactive {
composes: base;
cursor: pointer;
transition: box-shadow 0.2s ease;
}---
Tailwind CSS Integration
cn() Utility (clsx + tailwind-merge)
ALWAYS create a cn() utility that combines clsx for conditional classes with tailwind-merge to resolve Tailwind class conflicts:
// src/lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}Component Pattern
// Button.tsx
import { cn } from "@/lib/utils";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary";
size?: "sm" | "md" | "lg";
}
const variantStyles = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-transparent border border-blue-600 text-blue-600 hover:bg-blue-50",
} as const;
const sizeStyles = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg",
} as const;
export function Button({
variant = "primary",
size = "md",
className,
children,
...props
}: ButtonProps) {
return (
<button
className={cn(
"rounded-md font-semibold transition-colors",
variantStyles[variant],
sizeStyles[size],
className
)}
{...props}
>
{children}
</button>
);
}Responsive Design with Tailwind
Tailwind uses mobile-first breakpoints. ALWAYS design mobile-first and add breakpoint prefixes for larger screens:
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{items.map((item) => (
<Card key={item.id} className="p-4 sm:p-6" />
))}
</div>| Prefix | Min-width | Typical Device |
|---|---|---|
| (none) | 0px | Mobile |
sm: | 640px | Large phone / small tablet |
md: | 768px | Tablet |
lg: | 1024px | Laptop |
xl: | 1280px | Desktop |
2xl: | 1536px | Large desktop |
Dark Mode with Tailwind
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
<h1 className="text-2xl font-bold text-gray-800 dark:text-gray-200">
Dashboard
</h1>
</div>ALWAYS pair light and dark variants together on the same element for maintainability.
---
Inline Styles (Dynamic Values Only)
ALWAYS type inline styles with React.CSSProperties. Use inline styles ONLY for values computed at runtime from props or state:
interface ProgressBarProps {
value: number; // 0-100
color?: string;
}
export function ProgressBar({ value, color = "#3b82f6" }: ProgressBarProps) {
const barStyle: React.CSSProperties = {
width: `${Math.min(100, Math.max(0, value))}%`,
backgroundColor: color,
height: "0.5rem",
borderRadius: "0.25rem",
transition: "width 0.3s ease",
};
return (
<div style={{ backgroundColor: "#e5e7eb", borderRadius: "0.25rem" }}>
<div style={barStyle} role="progressbar" aria-valuenow={value} />
</div>
);
}NEVER use inline styles for static values -- move them to CSS Modules or Tailwind classes instead. Inline styles bypass the cascade, cannot be overridden by consumers, and increase bundle size.
---
className Patterns with clsx
The clsx library builds className strings from conditional inputs. ALWAYS use it instead of manual string concatenation:
import { clsx } from "clsx";
interface AlertProps {
severity: "info" | "warning" | "error";
dismissible?: boolean;
children: React.ReactNode;
}
export function Alert({ severity, dismissible = false, children }: AlertProps) {
return (
<div
className={clsx(
"rounded-md p-4 text-sm",
{
"bg-blue-50 text-blue-700": severity === "info",
"bg-yellow-50 text-yellow-700": severity === "warning",
"bg-red-50 text-red-700": severity === "error",
},
dismissible && "pr-10"
)}
role="alert"
>
{children}
</div>
);
}---
Global Styles and CSS Custom Properties
Global CSS Setup
ALWAYS define global design tokens as CSS custom properties in :root. This is the ONLY appropriate use for global CSS files:
/* src/index.css */
:root {
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--color-secondary: #64748b;
--color-background: #ffffff;
--color-surface: #f8fafc;
--color-text: #0f172a;
--color-text-muted: #64748b;
--color-border: #e2e8f0;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
}
/* Dark mode override */
@media (prefers-color-scheme: dark) {
:root {
--color-background: #0f172a;
--color-surface: #1e293b;
--color-text: #f1f5f9;
--color-text-muted: #94a3b8;
--color-border: #334155;
}
}
/* Reset */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
}Using Custom Properties in CSS Modules
/* Card.module.css */
.card {
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: var(--shadow-sm);
color: var(--color-text);
}---
Responsive Design with CSS Modules
Use standard media queries inside CSS Modules. ALWAYS use mobile-first (min-width) breakpoints:
/* Layout.module.css */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 640px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(3, 1fr); } }---
Dark Mode with CSS Custom Properties
For non-Tailwind projects, use CSS custom properties with a data-theme attribute and prefers-color-scheme fallback. Define dark overrides in :root[data-theme="dark"] (see Global Styles section above for the pattern). Toggle with:
import { useEffect, useState } from "react";
type Theme = "light" | "dark" | "system";
export function useTheme() {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem("theme") as Theme) ?? "system"
);
useEffect(() => {
const root = document.documentElement;
if (theme === "system") root.removeAttribute("data-theme");
else root.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}, [theme]);
return { theme, setTheme };
}---
React 19: Stylesheet Precedence
React 19 introduces built-in support for <link> stylesheet ordering. Use the precedence prop to control CSS load order without manual management:
// React 19 only
function ProductPage() {
return (
<>
<link rel="stylesheet" href="/base.css" precedence="default" />
<link rel="stylesheet" href="/product.css" precedence="high" />
<div className="product-layout">
<ProductDetails />
</div>
</>
);
}
function ProductDetails() {
// This stylesheet is deduplicated -- React loads it only once
return (
<>
<link rel="stylesheet" href="/product.css" precedence="high" />
<div className="product-details">{/* ... */}</div>
</>
);
}React 19 deduplicates stylesheet links and orders them by precedence. This eliminates the need for CSS-in-JS runtime ordering in many cases.
React 18: No precedence prop support. Use CSS Modules or manual <link> ordering in index.html.
---
CSS-in-JS Overview
CSS-in-JS libraries (styled-components, Emotion) co-locate styles with components and support dynamic theming. Use them ONLY when the project requires runtime theme switching or an existing codebase depends on them.
// styled-components pattern (brief reference)
import styled from "styled-components";
const StyledButton = styled.button<{ $variant: "primary" | "secondary" }>`
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-weight: 600;
background-color: ${(props) =>
props.$variant === "primary" ? "var(--color-primary)" : "transparent"};
color: ${(props) =>
props.$variant === "primary" ? "white" : "var(--color-primary)"};
`;NEVER choose CSS-in-JS for new projects without a specific runtime theming requirement. CSS Modules and Tailwind cover the vast majority of use cases with zero runtime cost.
---
Reference Links
- references/examples.md -- Complete styling approach examples with TypeScript
- references/anti-patterns.md -- Common styling mistakes and how to avoid them
Official Sources
- https://react.dev/learn#adding-styles
- https://react.dev/reference/react-dom/components/link (React 19 precedence)
- https://tailwindcss.com/docs
- https://github.com/lukeed/clsx
- https://github.com/dcastil/tailwind-merge
react-impl-styling — Anti-Patterns
AP-01: String Concatenation for className
NEVER build className strings with template literals or concatenation.
// BAD -- whitespace bugs, hard to read, no type safety
function Badge({ active, size }: { active: boolean; size: string }) {
return (
<span className={`badge ${active ? "badge-active" : ""} badge-${size}`}>
{/* Extra space when active is false: "badge badge-sm" */}
</span>
);
}
// GOOD -- use clsx for clean conditional composition
import { clsx } from "clsx";
function Badge({ active, size }: { active: boolean; size: "sm" | "md" | "lg" }) {
return (
<span
className={clsx("badge", active && "badge-active", `badge-${size}`)}
/>
);
}Why: String concatenation produces extra whitespace when conditions are false, is harder to read with multiple conditions, and provides no static analysis benefits.
---
AP-02: Inline Styles for Static Values
NEVER use inline styles for values that do not change at runtime.
// BAD -- static styles in inline object, recreated every render
function Sidebar() {
return (
<aside
style={{
width: "16rem",
backgroundColor: "#f8fafc",
borderRight: "1px solid #e2e8f0",
padding: "1.5rem",
minHeight: "100vh",
}}
>
{/* ... */}
</aside>
);
}
// GOOD -- static styles in CSS Module
// Sidebar.module.css:
// .sidebar { width: 16rem; background-color: var(--color-surface); ... }
import styles from "./Sidebar.module.css";
function Sidebar() {
return <aside className={styles.sidebar}>{/* ... */}</aside>;
}Why: Inline style objects are recreated every render (new object reference), cannot use pseudo-classes or media queries, cannot be overridden by consumers, and bloat the HTML output.
---
AP-03: Global CSS Without Scoping
NEVER import plain .css files in component files for component-specific styles.
// BAD -- .button class is global, will collide with other .button classes
import "./Button.css"; // NOT .module.css
function Button() {
return <button className="button">Click</button>;
}
// GOOD -- CSS Modules scope automatically
import styles from "./Button.module.css";
function Button() {
return <button className={styles.button}>Click</button>;
}Why: Plain CSS imports are global. Two components defining .card or .button will overwrite each other's styles unpredictably based on import order.
---
AP-04: CSS-in-JS in Server Components
NEVER use styled-components, Emotion, or other CSS-in-JS runtime libraries in React Server Components.
// BAD -- crashes in RSC because styled-components requires browser APIs
"use server"; // or no directive in app/ directory (RSC by default)
import styled from "styled-components";
const Container = styled.div`
max-width: 1200px;
margin: 0 auto;
`;
// GOOD -- use CSS Modules in Server Components
import styles from "./Layout.module.css";
export function Layout({ children }: { children: React.ReactNode }) {
return <div className={styles.container}>{children}</div>;
}Why: CSS-in-JS libraries inject styles at runtime using browser APIs (document.createElement, insertRule). These APIs do not exist on the server. Server Components NEVER run on the client.
---
AP-05: Tailwind Classes Without tailwind-merge
NEVER use clsx alone when merging Tailwind classes from external props -- conflicting utilities will both apply.
// BAD -- if className="p-8", both p-4 AND p-8 apply (unpredictable)
import { clsx } from "clsx";
function Card({ className }: { className?: string }) {
return <div className={clsx("rounded-lg p-4 bg-white", className)} />;
}
// GOOD -- tailwind-merge resolves conflicts (p-8 wins over p-4)
import { cn } from "@/lib/utils"; // clsx + twMerge
function Card({ className }: { className?: string }) {
return <div className={cn("rounded-lg p-4 bg-white", className)} />;
}Why: Tailwind utilities are atomic CSS classes. When two conflicting utilities exist (p-4 and p-8), the winner depends on CSS source order, not DOM order. tailwind-merge intelligently resolves conflicts by keeping only the last value for each CSS property.
---
AP-06: Hardcoded Colors Instead of Design Tokens
NEVER hardcode color values directly in component styles.
/* BAD -- hardcoded hex values scattered across files */
.header {
background-color: #1e293b;
color: #f1f5f9;
border-bottom: 1px solid #334155;
}
/* GOOD -- design tokens via CSS custom properties */
.header {
background-color: var(--color-surface);
color: var(--color-text);
border-bottom: 1px solid var(--color-border);
}Why: Hardcoded values make theming impossible, dark mode support painful, and design changes require find-and-replace across the entire codebase. CSS custom properties provide a single source of truth.
---
AP-07: Using style Prop for Pseudo-Classes
NEVER try to implement hover, focus, or active states with inline styles and event handlers.
// BAD -- recreating CSS with JavaScript, verbose, buggy, inaccessible
function BadButton() {
const [hovered, setHovered] = useState(false);
return (
<button
style={{
backgroundColor: hovered ? "#2563eb" : "#3b82f6",
color: "white",
padding: "0.5rem 1rem",
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
Click me
</button>
);
}
// GOOD -- CSS handles pseudo-classes natively
// Button.module.css:
// .button { background-color: var(--color-primary); }
// .button:hover { background-color: var(--color-primary-hover); }
// .button:focus-visible { outline: 2px solid var(--color-primary); }
import styles from "./Button.module.css";
function GoodButton() {
return <button className={styles.button}>Click me</button>;
}Why: JavaScript hover handlers miss keyboard focus states, do not handle touch correctly, cause unnecessary re-renders, and cannot handle :focus-visible or :active properly. CSS pseudo-classes are declarative, accessible, and performant.
---
AP-08: Mixing Styling Approaches Inconsistently
NEVER mix CSS Modules, Tailwind, and inline styles randomly within the same component.
// BAD -- three different styling approaches in one component
import styles from "./Card.module.css";
function Card({ highlight }: { highlight: boolean }) {
return (
<div
className={`${styles.card} flex flex-col`}
style={{ borderColor: highlight ? "red" : "gray" }}
>
<h2 className="text-lg font-bold">{/* Tailwind */}</h2>
<p className={styles.body}>{/* CSS Module */}</p>
</div>
);
}
// GOOD -- pick ONE primary approach and use it consistently
import { cn } from "@/lib/utils";
function Card({ highlight }: { highlight: boolean }) {
return (
<div
className={cn(
"flex flex-col rounded-lg border p-4",
highlight ? "border-red-500" : "border-gray-300"
)}
>
<h2 className="text-lg font-bold">{/* ... */}</h2>
<p className="text-gray-600">{/* ... */}</p>
</div>
);
}Why: Mixing approaches makes styles unpredictable (specificity wars), harder to maintain, and confusing for other developers. ALWAYS pick one primary approach per project.
---
AP-09: Not Forwarding className to Root Element
NEVER create reusable components that ignore the consumer's className prop.
// BAD -- consumers cannot customize the component
function Card({ children }: { children: React.ReactNode }) {
return <div className="rounded-lg border p-4">{children}</div>;
}
// Consumer: <Card className="mt-8" /> -- mt-8 is silently ignored!
// GOOD -- ALWAYS merge consumer className with internal classes
import { cn } from "@/lib/utils";
interface CardProps {
children: React.ReactNode;
className?: string;
}
function Card({ children, className }: CardProps) {
return (
<div className={cn("rounded-lg border p-4", className)}>
{children}
</div>
);
}Why: Reusable components MUST accept and forward className so consumers can control spacing, positioning, and overrides. Without this, every layout adjustment requires wrapper divs.
---
AP-10: Using !important to Fix Specificity
NEVER use !important to fix styling conflicts between components.
/* BAD -- specificity arms race */
.button {
padding: 0.5rem 1rem !important;
background-color: blue !important;
}
/* GOOD -- fix the root cause: use CSS Modules for scoping */
/* Or use CSS layers for controlled specificity */
@layer components {
.button {
padding: 0.5rem 1rem;
background-color: blue;
}
}Why: !important creates a specificity escalation that makes styles progressively harder to override. It indicates a scoping problem that should be solved with CSS Modules, CSS layers, or restructuring selectors.
react-impl-styling — Examples
CSS Modules: Complete Component System
Module Declaration
// src/types/css.d.ts
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module "*.module.scss" {
const classes: { readonly [key: string]: string };
export default classes;
}Card Component with CSS Modules
/* Card.module.css */
.card {
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
}
.header {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--color-border);
font-weight: 600;
font-size: 1.125rem;
color: var(--color-text);
}
.body {
padding: 1.5rem;
color: var(--color-text);
}
.footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--color-border);
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.clickable {
cursor: pointer;
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.clickable:hover {
box-shadow: var(--shadow-md);
transform: translateY(-1px);
}
.clickable:active {
transform: translateY(0);
}// Card.tsx
import styles from "./Card.module.css";
import { clsx } from "clsx";
interface CardProps {
children: React.ReactNode;
onClick?: () => void;
className?: string;
}
interface CardHeaderProps {
children: React.ReactNode;
}
interface CardBodyProps {
children: React.ReactNode;
}
interface CardFooterProps {
children: React.ReactNode;
}
export function Card({ children, onClick, className }: CardProps) {
const isClickable = typeof onClick === "function";
const Tag = isClickable ? "button" : "div";
return (
<Tag
className={clsx(styles.card, isClickable && styles.clickable, className)}
onClick={onClick}
type={isClickable ? "button" : undefined}
>
{children}
</Tag>
);
}
export function CardHeader({ children }: CardHeaderProps) {
return <div className={styles.header}>{children}</div>;
}
export function CardBody({ children }: CardBodyProps) {
return <div className={styles.body}>{children}</div>;
}
export function CardFooter({ children }: CardFooterProps) {
return <div className={styles.footer}>{children}</div>;
}Usage
<Card onClick={() => navigate(`/project/${project.id}`)}>
<CardHeader>Project Settings</CardHeader>
<CardBody>
<p>Configure project-level settings and permissions.</p>
</CardBody>
<CardFooter>
<Button variant="secondary">Cancel</Button>
<Button variant="primary">Save</Button>
</CardFooter>
</Card>---
Tailwind CSS: Complete Component System
cn() Utility Setup
// src/lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}Badge Component
// Badge.tsx
import { cn } from "@/lib/utils";
type BadgeVariant = "default" | "success" | "warning" | "error" | "info";
interface BadgeProps {
variant?: BadgeVariant;
children: React.ReactNode;
className?: string;
}
const variantStyles: Record<BadgeVariant, string> = {
default: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200",
success: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
warning: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
error: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
info: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
};
export function Badge({ variant = "default", children, className }: BadgeProps) {
return (
<span
className={cn(
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
variantStyles[variant],
className
)}
>
{children}
</span>
);
}Responsive Navigation
// Navigation.tsx
import { useState } from "react";
import { cn } from "@/lib/utils";
interface NavItem {
label: string;
href: string;
}
interface NavigationProps {
items: NavItem[];
}
export function Navigation({ items }: NavigationProps) {
const [isOpen, setIsOpen] = useState(false);
return (
<nav className="relative bg-white shadow dark:bg-gray-900">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="flex h-16 items-center justify-between">
<div className="flex-shrink-0">
<span className="text-xl font-bold text-gray-900 dark:text-white">
Logo
</span>
</div>
{/* Desktop menu */}
<div className="hidden sm:flex sm:items-center sm:gap-4">
{items.map((item) => (
<a
key={item.href}
href={item.href}
className="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white"
>
{item.label}
</a>
))}
</div>
{/* Mobile menu button */}
<button
type="button"
className="inline-flex items-center justify-center rounded-md p-2 text-gray-700 hover:bg-gray-100 sm:hidden dark:text-gray-300 dark:hover:bg-gray-800"
onClick={() => setIsOpen((prev) => !prev)}
aria-expanded={isOpen}
>
<span className="sr-only">Open main menu</span>
{isOpen ? "Close" : "Menu"}
</button>
</div>
</div>
{/* Mobile menu */}
<div className={cn("sm:hidden", isOpen ? "block" : "hidden")}>
<div className="space-y-1 px-2 pb-3 pt-2">
{items.map((item) => (
<a
key={item.href}
href={item.href}
className="block rounded-md px-3 py-2 text-base font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
{item.label}
</a>
))}
</div>
</div>
</nav>
);
}---
Inline Styles: Dynamic Value Patterns
Animated Progress Ring
interface ProgressRingProps {
value: number; // 0-100
size?: number;
strokeWidth?: number;
color?: string;
}
export function ProgressRing({
value,
size = 80,
strokeWidth = 6,
color = "#3b82f6",
}: ProgressRingProps) {
const radius = (size - strokeWidth) / 2;
const circumference = radius * 2 * Math.PI;
const offset = circumference - (value / 100) * circumference;
return (
<svg width={size} height={size} role="progressbar" aria-valuenow={value}>
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="#e5e7eb"
strokeWidth={strokeWidth}
/>
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={color}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
style={{
transition: "stroke-dashoffset 0.3s ease",
transform: "rotate(-90deg)",
transformOrigin: "50% 50%",
}}
/>
</svg>
);
}Grid Layout from Data
interface DashboardGridProps {
columns: number;
gap?: number;
children: React.ReactNode;
}
export function DashboardGrid({ columns, gap = 16, children }: DashboardGridProps) {
const gridStyle: React.CSSProperties = {
display: "grid",
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: `${gap}px`,
};
return <div style={gridStyle}>{children}</div>;
}---
CSS Custom Properties: Theming System
Complete Theme Setup
/* src/styles/tokens.css */
:root {
/* Colors */
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--color-primary-light: #dbeafe;
--color-secondary: #8b5cf6;
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* Surfaces */
--color-background: #ffffff;
--color-surface: #f8fafc;
--color-surface-elevated: #ffffff;
/* Text */
--color-text: #0f172a;
--color-text-secondary: #475569;
--color-text-muted: #94a3b8;
--color-text-inverse: #ffffff;
/* Borders */
--color-border: #e2e8f0;
--color-border-strong: #cbd5e1;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* Radii */
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
/* Typography */
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: "JetBrains Mono", "Fira Code", monospace;
}
:root[data-theme="dark"] {
--color-background: #0f172a;
--color-surface: #1e293b;
--color-surface-elevated: #334155;
--color-text: #f1f5f9;
--color-text-secondary: #cbd5e1;
--color-text-muted: #64748b;
--color-border: #334155;
--color-border-strong: #475569;
--color-primary-light: #1e3a5f;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
}Using Tokens in CSS Modules
/* Input.module.css */
.input {
width: 100%;
padding: var(--space-2) var(--space-3);
font-family: var(--font-sans);
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--color-text);
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.input:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-light);
}
.input::placeholder {
color: var(--color-text-muted);
}
.error {
border-color: var(--color-error);
}
.error:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}// Input.tsx
import styles from "./Input.module.css";
import { clsx } from "clsx";
import { forwardRef } from "react";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: boolean;
}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ error = false, className, ...props }, ref) => {
return (
<input
ref={ref}
className={clsx(styles.input, error && styles.error, className)}
aria-invalid={error}
{...props}
/>
);
}
);
Input.displayName = "Input";---
React 19: Stylesheet Precedence Example
// Layout.tsx (React 19)
function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<link rel="stylesheet" href="/styles/reset.css" precedence="reset" />
<link rel="stylesheet" href="/styles/tokens.css" precedence="default" />
<link rel="stylesheet" href="/styles/layout.css" precedence="default" />
<main>{children}</main>
</>
);
}
// Feature.tsx (React 19) -- styles loaded on demand
function FeaturePage() {
return (
<>
<link rel="stylesheet" href="/styles/feature.css" precedence="high" />
<section className="feature-hero">{/* ... */}</section>
</>
);
}React 19 handles deduplication automatically: if multiple components reference the same stylesheet href, it is loaded only once. The precedence value controls insertion order in <head>.