Responsive Design
> **Skill Purpose:** Mobile-first design implementation, breakpoint management, and responsive component patterns
SKILL.md
Full skill instructions
Responsive Design
Skill Purpose: Mobile-first design implementation, breakpoint management, and responsive component patterns
Core Skill Pattern
Objective: Establish comprehensive responsive design strategy with mobile-first approach, breakpoint management, and cross-device compatibility.
Universal Pattern:
- Define responsive design strategy and approach
- Create breakpoint management and grid systems
- Establish responsive component patterns
- Set up mobile-first design procedures
- Create responsive testing and validation procedures
Key Decisions (Project-Specific):
- Breakpoint strategy and screen size targets
- Mobile-first vs desktop-first approach
- Grid system and layout framework
- Component responsive behavior patterns
- Testing approach and device coverage
Project-Specific Implementation Notes
Customize per project:
- Breakpoints based on target devices and usage patterns
- Grid system based on layout complexity and framework
- Component patterns based on UI library and design system
- Testing depth based on device support requirements
- Performance considerations for mobile devices
Example Implementation (Tailwind CSS Responsive Pattern)
Note: This is an example pattern using Tailwind CSS with mobile-first design. Adapt responsive framework and patterns based on your specific project requirements and design system.
Prerequisites (Example)
- Tailwind CSS configured and integrated
- Design system breakpoints defined
- Component library (shadcn/ui) set up
- Responsive testing tools configured
Example: Responsive Design Implementation
Framework-Specific Example: This demonstrates responsive patterns with Tailwind CSS and Next.js. Adapt for your responsive framework and component library requirements.
Responsive Configuration
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
screens: {
'xs': '475px', // Extra small screens
'sm': '640px', // Small screens
'md': '768px', // Medium screens (tablets)
'lg': '1024px', // Large screens (laptops)
'xl': '1280px', // Extra large screens
'2xl': '1536px', // 2X large screens
},
container: {
center: true,
padding: {
DEFAULT: '1rem',
sm: '2rem',
lg: '4rem',
xl: '5rem',
'2xl': '6rem',
},
},
},
},
plugins: [],
};
export default config;
Responsive Grid System
// components/ui/responsive-grid.tsx
import React from 'react';
import { cn } from '@/lib/utils';
interface ResponsiveGridProps {
children: React.ReactNode;
cols?: {
xs?: number;
sm?: number;
md?: number;
lg?: number;
xl?: number;
'2xl'?: number;
};
gap?: string;
className?: string;
}
export function ResponsiveGrid({
children,
cols = { xs: 1, sm: 2, md: 3, lg: 4 },
gap = 'gap-4',
className
}: ResponsiveGridProps) {
const gridClasses = cn(
'grid',
gap,
{
'grid-cols-1': cols.xs === 1,
'grid-cols-2': cols.xs === 2,
'grid-cols-3': cols.xs === 3,
'grid-cols-4': cols.xs === 4,
'grid-cols-5': cols.xs === 5,
'grid-cols-6': cols.xs === 6,
},
{
'sm:grid-cols-1': cols.sm === 1,
'sm:grid-cols-2': cols.sm === 2,
'sm:grid-cols-3': cols.sm === 3,
'sm:grid-cols-4': cols.sm === 4,
'sm:grid-cols-5': cols.sm === 5,
'sm:grid-cols-6': cols.sm === 6,
},
{
'md:grid-cols-1': cols.md === 1,
'md:grid-cols-2': cols.md === 2,
'md:grid-cols-3': cols.md === 3,
'md:grid-cols-4': cols.md === 4,
'md:grid-cols-5': cols.md === 5,
'md:grid-cols-6': cols.md === 6,
},
{
'lg:grid-cols-1': cols.lg === 1,
'lg:grid-cols-2': cols.lg === 2,
'lg:grid-cols-3': cols.lg === 3,
'lg:grid-cols-4': cols.lg === 4,
'lg:grid-cols-5': cols.lg === 5,
'lg:grid-cols-6': cols.lg === 6,
},
{
'xl:grid-cols-1': cols.xl === 1,
'xl:grid-cols-2': cols.xl === 2,
'xl:grid-cols-3': cols.xl === 3,
'xl:grid-cols-4': cols.xl === 4,
'xl:grid-cols-5': cols.xl === 5,
'xl:grid-cols-6': cols.xl === 6,
},
className
);
return <div className={gridClasses}>{children}</div>;
}
Responsive Container Component
// components/ui/responsive-container.tsx
import React from 'react';
import { cn } from '@/lib/utils';
interface ResponsiveContainerProps {
children: React.ReactNode;
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
className?: string;
}
export function ResponsiveContainer({
children,
size = 'lg',
className
}: ResponsiveContainerProps) {
const sizeClasses = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
full: 'max-w-full'
};
return (
<div className={cn(
'mx-auto px-4 sm:px-6 lg:px-8',
sizeClasses[size],
className
)}>
{children}
</div>
);
}
Responsive Navigation Component
// components/ui/responsive-navigation.tsx
import React, { useState } from 'react';
import { AccessibleButton } from './accessible-button';
interface ResponsiveNavigationProps {
items: Array<{
label: string;
href: string;
active?: boolean;
}>;
}
export function ResponsiveNavigation({ items }: ResponsiveNavigationProps) {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
return (
<nav className="bg-white shadow-sm" role="navigation" aria-label="Main navigation">
<div className="mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
{/* Logo/Brand - visible on all screens */}
<div className="flex items-center">
<div className="flex-shrink-0">
<h1 className="text-xl font-bold">Brand</h1>
</div>
</div>
{/* Desktop Navigation - hidden on mobile */}
<div className="hidden md:flex items-center space-x-8">
{items.map((item) => (
<a
key={item.href}
href={item.href}
className={cn(
'px-3 py-2 rounded-md text-sm font-medium',
item.active
? 'bg-primary text-primary-foreground'
: 'text-gray-700 hover:text-gray-900 hover:bg-gray-100'
)}
aria-current={item.active ? 'page' : undefined}
>
{item.label}
</a>
))}
</div>
{/* Mobile menu button - visible only on mobile */}
<div className="md:hidden flex items-center">
<AccessibleButton
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-expanded={isMobileMenuOpen}
aria-label="Toggle navigation menu"
className="p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100"
>
<svg
className="h-6 w-6"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
stroke="currentColor"
>
{isMobileMenuOpen ? (
<path d="M6 18L18 6M6 6l12 12" />
) : (
<path d="M4 6h16M4 12h16M4 18h16" />
)}
</svg>
</AccessibleButton>
</div>
</div>
{/* Mobile Navigation Menu - visible only when open on mobile */}
{isMobileMenuOpen && (
<div className="md:hidden">
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
{items.map((item) => (
<a
key={item.href}
href={item.href}
className={cn(
'block px-3 py-2 rounded-md text-base font-medium',
item.active
? 'bg-primary text-primary-foreground'
: 'text-gray-700 hover:text-gray-900 hover:bg-gray-100'
)}
aria-current={item.active ? 'page' : undefined}
onClick={() => setIsMobileMenuOpen(false)}
>
{item.label}
</a>
))}
</div>
</div>
)}
</div>
</nav>
);
}
Responsive Card Component
// components/ui/responsive-card.tsx
import React from 'react';
import { cn } from '@/lib/utils';
interface ResponsiveCardProps {
children: React.ReactNode;
variant?: 'default' | 'compact' | 'expanded';
className?: string;
}
export function ResponsiveCard({
children,
variant = 'default',
className
}: ResponsiveCardProps) {
const variantClasses = {
default: 'p-4 sm:p-6',
compact: 'p-3 sm:p-4',
expanded: 'p-6 sm:p-8'
};
return (
<div className={cn(
'bg-white rounded-lg shadow-sm border border-gray-200',
'transition-shadow duration-200 hover:shadow-md',
variantClasses[variant],
className
)}>
{children}
</div>
);
}
// Usage example with responsive content
export function ResponsiveProductCard({ product }: { product: any }) {
return (
<ResponsiveCard variant="compact">
<div className="space-y-4">
{/* Product image - responsive sizing */}
<div className="aspect-square w-full sm:w-48 sm:h-48 mx-auto bg-gray-100 rounded-lg overflow-hidden">
<img
src={product.image}
alt={product.name}
className="w-full h-full object-cover"
/>
</div>
{/* Product info - responsive text sizing */}
<div className="text-center sm:text-left">
<h3 className="text-lg sm:text-xl font-semibold text-gray-900 line-clamp-2">
{product.name}
</h3>
<p className="text-sm sm:text-base text-gray-600 mt-1 line-clamp-3">
{product.description}
</p>
<div className="mt-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<span className="text-xl sm:text-2xl font-bold text-primary">
${product.price}
</span>
<AccessibleButton className="w-full sm:w-auto">
Add to Cart
</AccessibleButton>
</div>
</div>
</div>
</ResponsiveCard>
);
}
Responsive Layout Component
// components/ui/responsive-layout.tsx
import React from 'react';
import { ResponsiveContainer } from './responsive-container';
import { ResponsiveNavigation } from './responsive-navigation';
interface ResponsiveLayoutProps {
children: React.ReactNode;
navigation?: Array<{
label: string;
href: string;
active?: boolean;
}>;
}
export function ResponsiveLayout({ children, navigation }: ResponsiveLayoutProps) {
return (
<div className="min-h-screen bg-gray-50">
{/* Responsive Navigation */}
{navigation && <ResponsiveNavigation items={navigation} />}
{/* Main Content */}
<main id="main-content" className="py-6 sm:py-8 lg:py-12">
<ResponsiveContainer size="xl">
{children}
</ResponsiveContainer>
</main>
{/* Responsive Footer */}
<footer className="bg-white border-t border-gray-200 mt-auto">
<ResponsiveContainer size="lg">
<div className="py-8 sm:py-12">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{/* Footer content with responsive columns */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">Company</h3>
<ul className="space-y-2">
<li><a href="#" className="text-gray-600 hover:text-gray-900">About</a></li>
<li><a href="#" className="text-gray-600 hover:text-gray-900">Contact</a></li>
</ul>
</div>
{/* Additional footer sections */}
</div>
</div>
</ResponsiveContainer>
</footer>
</div>
);
}
Responsive Utilities
// lib/responsive-utils.ts
export class ResponsiveUtils {
static getBreakpoint(): string {
if (typeof window === 'undefined') return 'lg';
const width = window.innerWidth;
if (width < 475) return 'xs';
if (width < 640) return 'sm';
if (width < 768) return 'md';
if (width < 1024) return 'lg';
if (width < 1280) return 'xl';
return '2xl';
}
static isMobile(): boolean {
return this.getBreakpoint() === 'xs' || this.getBreakpoint() === 'sm';
}
static isTablet(): boolean {
return this.getBreakpoint() === 'md';
}
static isDesktop(): boolean {
return ['lg', 'xl', '2xl'].includes(this.getBreakpoint());
}
static useResponsiveValue<T>(values: Partial<Record<string, T>>): T {
const breakpoint = this.getBreakpoint();
// Find the most specific value for current breakpoint
const breakpointOrder = ['2xl', 'xl', 'lg', 'md', 'sm', 'xs'];
for (const bp of breakpointOrder) {
if (breakpoint === bp && values[bp] !== undefined) {
return values[bp]!;
}
}
// Fallback to next smaller breakpoint
for (const bp of breakpointOrder) {
if (values[bp] !== undefined) {
return values[bp]!;
}
}
throw new Error('No responsive value provided');
}
static generateResponsiveClasses(
baseClass: string,
responsiveValues: Partial<Record<string, string>>
): string {
const classes = [baseClass];
Object.entries(responsiveValues).forEach(([breakpoint, value]) => {
if (breakpoint === 'default') {
classes.push(value);
} else {
classes.push(`${breakpoint}:${value}`);
}
});
return classes.join(' ');
}
}
Best Practices
- Mobile-first approach - Design for mobile first, then enhance for larger screens
- Consistent breakpoints - Use standard breakpoint sizes across the application
- Flexible grids - Use CSS Grid and Flexbox for adaptive layouts
- Responsive images - Optimize images for different screen sizes and resolutions
- Touch-friendly targets - Ensure interactive elements are appropriately sized for touch
Stop Conditions
STOP and escalate if:
- Responsive strategy unclear or inconsistent
- Breakpoint definitions not established
- Mobile-first approach not defined
- Testing strategy for responsive design missing
Skill Version: 1.0.0
