Next.js Integration
Integration of the LYD Design System into Next.js projects
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:
# 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
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.tsxOption 1
Option 2
Das HeroUI-inspirierte System löst alle Z-Index-Probleme durch Portal-Rendering.