@winzenburg/react
v2.0.0
Published
React components for the design system
Maintainers
Readme
@winzenburg/react
Production-ready React components built with accessibility and performance in mind
A comprehensive library of accessible, performant React components built on top of our design token system. Every component is designed to meet WCAG 2.2 AA standards and provides excellent developer experience with TypeScript support.
Installation
# Install both packages (tokens required for styling)
npm install @winzenburg/react @winzenburg/tokens
# Or with yarn
yarn add @winzenburg/react @winzenburg/tokensBasic Setup
// 1. Import CSS tokens in your app entry point (required)
import '@winzenburg/tokens/css'
// 2. Import and use components
import { Button, Stack, Typography } from '@winzenburg/react'
function App() {
return (
<Stack gap="4" align="center">
<Typography variant="h1">Welcome to our Design System</Typography>
<Button variant="solid" size="lg" onClick={() => alert('Hello!')}>
Get Started
</Button>
</Stack>
)
}Import Styles
The component styles are automatically included, but you need the design tokens CSS:
Option 1: CSS Import (Recommended)
/* In your main CSS file */
@import '@winzenburg/tokens/css';Option 2: JavaScript Import
// In your app entry point (index.js, App.js, etc.)
import '@winzenburg/tokens/css'Option 3: Individual Component Styles
// Import specific component styles if needed
import '@winzenburg/react/styles'Components
Form Elements
import {
Button,
Input,
Textarea,
Select,
Checkbox,
Radio,
Switch
} from '@winzenburg/react'
function ContactForm() {
return (
<form>
<Input
label="Full Name"
placeholder="Enter your name"
required
/>
<Input
type="email"
label="Email Address"
placeholder="Enter your email"
required
/>
<Textarea
label="Message"
placeholder="Tell us about your project"
rows={4}
/>
<Checkbox label="Subscribe to updates" />
<Button type="submit" variant="solid" size="lg">
Send Message
</Button>
</form>
)
}Layout Components
import { Stack, Grid, Card, Container } from '@winzenburg/react'
function ProductGrid() {
return (
<Container maxWidth="lg">
<Grid columns={{ base: 1, md: 2, lg: 3 }} gap="6">
{products.map(product => (
<Card key={product.id} padding="6">
<Stack gap="3">
<Typography variant="h3">{product.name}</Typography>
<Typography variant="body" color="muted">
{product.description}
</Typography>
<Button variant="outline" size="sm">
Learn More
</Button>
</Stack>
</Card>
))}
</Grid>
</Container>
)
}Navigation Components
import { Tabs, Breadcrumb, Link } from '@winzenburg/react'
function Navigation() {
return (
<div>
<Breadcrumb>
<Link href="/">Home</Link>
<Link href="/products">Products</Link>
<span>Detail</span>
</Breadcrumb>
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="features">Features</Tabs.Trigger>
<Tabs.Trigger value="specs">Specifications</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">
<Typography>Product overview content...</Typography>
</Tabs.Content>
<Tabs.Content value="features">
<Typography>Product features content...</Typography>
</Tabs.Content>
<Tabs.Content value="specs">
<Typography>Product specifications...</Typography>
</Tabs.Content>
</Tabs>
</div>
)
}Import Patterns
Barrel Imports (Convenience)
// Import multiple components at once
import { Button, Input, Stack, Typography } from '@winzenburg/react'Individual Imports (Optimal Bundle Size)
// Import individual components for better tree-shaking
import { Button } from '@winzenburg/react/components/Button'
import { Input } from '@winzenburg/react/components/Input'
import { Stack } from '@winzenburg/react/components/Stack'Utility Imports
// Import utility functions
import { cn } from '@winzenburg/react/utils'
// Usage
<div className={cn('base-class', { 'conditional-class': condition })} />TypeScript Support
Full TypeScript support with comprehensive type definitions:
import { Button, ButtonProps } from '@winzenburg/react'
// Component props are fully typed
function CustomButton(props: ButtonProps) {
return <Button {...props} />
}
// Ref forwarding support
function MyComponent() {
const buttonRef = useRef<HTMLButtonElement>(null)
return (
<Button
ref={buttonRef}
variant="solid"
size="lg"
onClick={() => console.log('Clicked!')}
>
Click me
</Button>
)
}Styling and Customization
Using Design Tokens
Components automatically use design tokens, but you can customize with CSS variables:
.custom-button {
/* Override component-specific tokens */
--button-bg: var(--ds-color-purple-500);
--button-fg: var(--ds-color-white);
/* Or use any design system token */
border-radius: var(--ds-radius-lg);
box-shadow: var(--ds-shadow-xl);
}Custom Styling with className
import { Button } from '@winzenburg/react'
import styles from './MyComponent.module.css'
function MyComponent() {
return (
<Button
className={styles.customButton}
variant="outline"
>
Custom Styled Button
</Button>
)
}Style Props
Some components support style props for common customizations:
<Stack
gap="4"
padding="6"
borderRadius="md"
backgroundColor="gray.50"
>
<Typography>Styled with props</Typography>
</Stack>Accessibility Features
All components are built with accessibility in mind:
Keyboard Navigation
- Full keyboard support for all interactive elements
- Proper focus management and visual indicators
- Arrow key navigation for complex components
Screen Reader Support
- Semantic HTML elements
- Proper ARIA labels, roles, and properties
- Live region announcements for dynamic content
High Contrast Support
- Works with Windows High Contrast mode
- Respects
prefers-contrastmedia query - Sufficient color contrast ratios (4.5:1 minimum)
Example: Accessible Form
import { Input, Button, Stack } from '@winzenburg/react'
function AccessibleForm() {
return (
<form role="form" aria-label="Contact form">
<Stack gap="4">
<Input
id="name"
label="Full Name"
placeholder="Enter your full name"
required
aria-describedby="name-help"
/>
<div id="name-help">We'll use this to personalize your experience</div>
<Input
type="email"
id="email"
label="Email Address"
placeholder="Enter your email"
required
aria-invalid={emailError ? 'true' : 'false'}
aria-describedby={emailError ? 'email-error' : undefined}
/>
{emailError && (
<div id="email-error" role="alert">
Please enter a valid email address
</div>
)}
<Button type="submit" variant="solid">
Submit Form
</Button>
</Stack>
</form>
)
}Performance
Bundle Size
- Full library: ~15KB gzipped
- Individual components: 1-5KB each when tree-shaken
- CSS: Included automatically, ~3KB additional
Tree Shaking
Import only what you need for optimal bundle sizes:
// ✅ Good - Only imports Button component
import { Button } from '@winzenburg/react/components/Button'
// ❌ Imports entire library (still tree-shakeable but less optimal)
import { Button } from '@winzenburg/react'Server-Side Rendering (SSR)
All components support SSR with frameworks like Next.js, Remix, and Gatsby:
// Works out of the box with SSR
import { Button } from '@winzenburg/react'
export default function Page() {
return <Button>Server-rendered button</Button>
}Theme Support
Built-in Theme Utilities
The library includes built-in utilities for theme management that handle SSR compatibility and provide a clean API:
import { setTheme, setThemeOnly, setProductOnly, getCurrentTheme, resetTheme } from '@winzenburg/react'
// Set both theme and product preset
setTheme('dark', 'trust')
// Set only theme (preserves existing product)
setThemeOnly('light')
// Set only product preset (preserves existing theme)
setProductOnly('momentum')
// Get current theme settings
const { theme, product } = getCurrentTheme()
// Reset to defaults
resetTheme()Theme Toggle Component Example
import { useState, useEffect } from 'react'
import { Button } from '@winzenburg/react'
import { setTheme, getCurrentTheme } from '@winzenburg/react'
function ThemeToggle() {
const [currentTheme, setCurrentTheme] = useState('light')
const [currentProduct, setCurrentProduct] = useState('trust')
// Initialize from current DOM state
useEffect(() => {
const current = getCurrentTheme()
if (current.theme) setCurrentTheme(current.theme)
if (current.product) setCurrentProduct(current.product)
}, [])
const toggleTheme = () => {
const newTheme = currentTheme === 'light' ? 'dark' : 'light'
setTheme(newTheme, currentProduct)
setCurrentTheme(newTheme)
}
return (
<Button
variant="ghost"
onClick={toggleTheme}
aria-label={`Switch to ${currentTheme === 'light' ? 'dark' : 'light'} theme`}
>
{currentTheme === 'light' ? 'Dark' : 'Light'} Mode
</Button>
)
}App Layout Integration
// In your app root or layout component
import { useEffect, useState } from 'react'
import { setTheme, getCurrentTheme } from '@winzenburg/react'
function AppLayout({ children }) {
const [isLoaded, setIsLoaded] = useState(false)
useEffect(() => {
// Initialize theme on app start
const savedTheme = localStorage.getItem('theme') || 'light'
const savedProduct = localStorage.getItem('product') || 'trust'
setTheme(savedTheme, savedProduct)
setIsLoaded(true)
}, [])
const handleThemeChange = (theme, product) => {
setTheme(theme, product)
localStorage.setItem('theme', theme)
localStorage.setItem('product', product)
}
if (!isLoaded) {
return <div>Loading...</div> // Prevent flash of unstyled content
}
return (
<div className="app-layout">
<header>
<ThemeToggle onThemeChange={handleThemeChange} />
</header>
<main>{children}</main>
</div>
)
}Product Presets
The design system supports multiple product emotional presets:
import { setProductOnly } from '@winzenburg/react'
// Available presets: 'trust', 'momentum', 'inspiration', 'calm', 'rhythm', 'care'
const presets = [
{ id: 'trust', name: 'Trust', description: 'Professional, stable, reliable' },
{ id: 'momentum', name: 'Momentum', description: 'Energetic, forward-moving' },
{ id: 'inspiration', name: 'Inspiration', description: 'Creative, imaginative' },
{ id: 'calm', name: 'Calm', description: 'Peaceful, soothing' },
{ id: 'rhythm', name: 'Rhythm', description: 'Systematic, efficient' },
{ id: 'care', name: 'Care', description: 'Professional, trustworthy' }
]
function PresetSelector() {
return (
<div>
{presets.map(preset => (
<Button
key={preset.id}
variant="outline"
onClick={() => setProductOnly(preset.id)}
>
{preset.name}
</Button>
))}
</div>
)
}SSR-Safe Theme Management
The theme utilities automatically handle server-side rendering:
// ✅ Safe to call during SSR - will log warning but not crash
useEffect(() => {
setTheme('dark', 'momentum')
}, [])
// ✅ Safe to call during SSR - returns null values
const { theme, product } = getCurrentTheme()Custom Brand Themes
/* Define custom theme */
[data-theme="brand"] {
--ds-color-primary-500: #your-brand-color;
--ds-color-primary-600: #your-darker-shade;
}<div data-theme="brand">
<Button variant="solid">Branded Button</Button>
</div>Component API Patterns
Consistent Prop Patterns
All components follow consistent patterns:
interface ComponentProps {
// Standard HTML props
className?: string
style?: React.CSSProperties
children?: React.ReactNode
// Component-specific variants
variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
size?: 'sm' | 'md' | 'lg'
// State props (when applicable)
disabled?: boolean
loading?: boolean
// Controlled/uncontrolled patterns
value?: string
defaultValue?: string
onChange?: (value: string) => void
// Event handlers
onPress?: () => void
onFocus?: () => void
onBlur?: () => void
}Ref Forwarding
All components support ref forwarding:
const buttonRef = useRef<HTMLButtonElement>(null)
<Button ref={buttonRef}>Click me</Button>Polymorphic Components
Some components support the as prop:
// Render as different HTML elements
<Button as="a" href="/link">Link Button</Button>
<Typography as="h1">Heading</Typography>
<Stack as="section">Section Content</Stack>Testing
Testing Library Integration
Components work seamlessly with React Testing Library:
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from '@winzenburg/react'
test('button click handler', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click me</Button>)
fireEvent.click(screen.getByRole('button', { name: /click me/i }))
expect(handleClick).toHaveBeenCalledTimes(1)
})Accessibility Testing
import { axe, toHaveNoViolations } from 'jest-axe'
expect.extend(toHaveNoViolations)
test('should not have accessibility violations', async () => {
const { container } = render(<Button>Accessible button</Button>)
const results = await axe(container)
expect(results).toHaveNoViolations()
})Browser Support
| Browser | Version | |---------|---------| | Chrome | 88+ | | Firefox | 85+ | | Safari | 14+ | | Edge | 88+ |
Migration Guide
From v0.x to v1.x
Breaking changes and migration steps:
Import paths changed:
// Before import Button from '@winzenburg/react/Button' // After import { Button } from '@winzenburg/react'Prop renames:
// Before <Button type="primary" /> // After <Button variant="solid" />CSS import required:
// Add this to your app import '@winzenburg/tokens/css'
See CHANGELOG.md for complete migration guide.
Contributing
See the main Contributing Guide for development setup and contribution guidelines.
Examples Repository
Check out our examples repository for:
- Next.js integration
- Remix integration
- Storybook setup
- Custom theming examples
- Advanced component compositions
Support
- 📚 Storybook Documentation - Interactive component docs
- 🐛 Issues - Bug reports and feature requests
- 💬 Discussions - Community Q&A
License
MIT © Your Organization
