Supported Versions

Next.js 13+ React 18+ TypeScript 5+ App Router Pages Router

The LYD Design System is fully compatible with the latest versions of Next.js and supports both App Router and Pages Router.

Installation

CSS Integration

Copy the master.css to your Next.js project:

Terminal
# Create the public/css folder mkdir -p public/css # Copy the master.css cp path/to/master.css public/css/master.css

Include Global CSS (App Router)

app/layout.tsx
import './globals.css' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="de"> <head> <link rel="stylesheet" href="/css/master.css" /> </head> <body>{children}</body> </html> ) }

Component Library Setup

Create reusable React Components:

components/Button.tsx
import React from 'react' interface ButtonProps { variant?: 'primary' | 'secondary' | 'outline' | 'ghost' children: React.ReactNode onClick?: () => void icon?: React.ReactNode } export const Button: React.FC<ButtonProps> = ({ variant = 'primary', children, onClick, icon }) => { return ( <button className={`lyd-button ${variant}`} onClick={onClick} > {icon && <span className="lyd-icon">{icon}</span>} {children} </button> ) }

Component Examples

Button Component with TypeScript

components/ui/Button.tsx
import { ButtonHTMLAttributes, FC } from 'react' import { cn } from '@/lib/utils' interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'luxury' size?: 'small' | 'default' | 'large' loading?: boolean icon?: React.ReactNode } export const Button: FC<ButtonProps> = ({ className, variant = 'primary', size = 'default', loading = false, disabled, children, icon, ...props }) => { return ( <button className={cn( 'lyd-button', variant, size, loading && 'loading', className )} disabled={disabled || loading} {...props} > {icon && !loading && ( <svg className="lyd-icon" viewBox="0 0 24 24"> {icon} </svg> )} {loading && <span className="luxury-spinner" />} {children} </button> ) }

Card Component

components/ui/Card.tsx
import { FC, ReactNode } from 'react' interface CardProps { title?: string subtitle?: string children: ReactNode image?: string actions?: ReactNode elevated?: boolean } export const Card: FC<CardProps> = ({ title, subtitle, children, image, actions, elevated = false }) => { return ( <div className={`lyd-card ${elevated ? 'elevated' : ''}`}> {image && ( <img src={image} alt="" className="lyd-card-image" /> )} {(title || subtitle) && ( <div className="lyd-card-header"> {title && <h3 className="lyd-card-title">{title}</h3>} {subtitle && <p className="lyd-card-subtitle">{subtitle}</p>} </div> )} <div className="lyd-card-body"> {children} </div> {actions && ( <div className="lyd-card-footer"> {actions} </div> )} </div> ) }

Client Components

Interactive Components with "use client"

components/ui/Modal.tsx
'use client' import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' interface ModalProps { isOpen: boolean onClose: () => void title?: string children: React.ReactNode } export function Modal({ isOpen, onClose, title, children }: ModalProps) { const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) return () => setMounted(false) }, []) if (!mounted || !isOpen) return null return createPortal( <div className="lyd-modal-backdrop" onClick={onClose}> <div className="lyd-modal" onClick={(e) => e.stopPropagation()} > {title && ( <div className="lyd-modal-header"> <h2>{title}</h2> <button onClick={onClose} className="lyd-modal-close"> × </button> </div> )} <div className="lyd-modal-body"> {children} </div> </div> </div>, document.body ) }

Form Components

Form with React Hook Form

app/contact/page.tsx
'use client' import { useForm } from 'react-hook-form' import { Button } from '@/components/ui/Button' export default function ContactForm() { const { register, handleSubmit, formState: { errors } } = useForm() const onSubmit = (data: any) => { console.log(data) } return ( <form onSubmit={handleSubmit(onSubmit)}> <div className="lyd-input-group"> <label className="lyd-input-label">Name</label> <input className="lyd-input" {...register('name', { required: true })} /> {errors.name && ( <span className="lyd-input-error"> Name is required </span> )} </div> <div className="lyd-input-group"> <label className="lyd-input-label">Email</label> <input type="email" className="lyd-input" {...register('email', { required: true })} /> </div> <Button type="submit" variant="primary"> Submit </Button> </form> ) }

Server Components

Server Component with Data Fetching

app/properties/page.tsx
import { Card } from '@/components/ui/Card' async function getProperties() { const res = await fetch('https://api.example.com/properties', { next: { revalidate: 3600 } }) return res.json() } export default async function PropertiesPage() { const properties = await getProperties() return ( <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> {properties.map((property: any) => ( <Card key={property.id} title={property.title} subtitle={property.location} image={property.image} > <p>{property.description}</p> <p className="font-bold"> €{property.price.toLocaleString()} </p> </Card> ))} </div> ) }

Optimierung

Performance Best Practices

  • Tree Shaking: Import only required components
  • CSS Modules: Combine master.css with CSS Modules for component-specific styles
  • Image Optimization: Use Next.js Image Component
  • Lazy Loading: Dynamic imports for large components
  • Bundle Size: Analyze with @next/bundle-analyzer
Lazy Loading Example
import dynamic from 'next/dynamic' const Modal = dynamic( () => import('@/components/ui/Modal'), { loading: () => <div>Loading...</div>, ssr: false } )

Testing

Component Testing with Jest & Testing Library

__tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react' import { Button } from '@/components/ui/Button' describe('Button Component', () => { it('renders with text', () => { render(<Button>Click me</Button>) expect(screen.getByText('Click me')).toBeInTheDocument() }) it('handles click events', () => { const handleClick = jest.fn() render(<Button onClick={handleClick}>Click</Button>) fireEvent.click(screen.getByText('Click')) expect(handleClick).toHaveBeenCalledTimes(1) }) it('applies variant classes', () => { const { container } = render( <Button variant="secondary">Secondary</Button> ) expect(container.firstChild).toHaveClass('lyd-button secondary') }) })

Dropdown Integration

HeroUI-Pattern Dropdown in Next.js

components/ui/Select.tsx
Option 1
Option 2

Das HeroUI-inspirierte System löst alle Z-Index-Probleme durch Portal-Rendering.