Files
leptos-shadcn-ui/docs/components/mobile-design-guidelines.md
Ubuntu 1b8d4f4a2f drover: task-1767765269702426639
Task: Create mobile design guidelines
2026-01-10 09:24:39 +00:00

21 KiB

Mobile Design Guidelines

This guide documents the mobile-first design patterns and responsive design principles used throughout the Leptos ShadCN UI component library.

Table of Contents

Overview

Leptos ShadCN UI follows a mobile-first approach using Tailwind CSS's responsive utility classes. All components are designed to work seamlessly on mobile devices (320px+) and progressively enhance for larger screens.

Key Principles

  1. Mobile First: Start with mobile layouts, enhance for larger screens
  2. Touch Friendly: All interactive elements meet minimum touch target sizes
  3. Progressive Enhancement: Add complexity only when screen real estate allows
  4. Performance First: Optimize for mobile network conditions and device constraints
  5. Accessible: Maintain WCAG 2.1 AA compliance across all viewport sizes

Design Philosophy

Mobile-First Approach

We design for the smallest screen first, then add complexity for larger screens. This ensures:

  • Core functionality works on all devices
  • Performance is optimized for mobile constraints
  • Content is prioritized over decoration
  • Progressive enhancement is natural

Responsive Breakpoints

Our breakpoint system aligns with common device sizes:

Breakpoint Min Width Target Devices Use Case
(default) 0px Mobile phones Base styles, single column layouts
sm: 640px Large phones, landscape Two-column grids, enhanced spacing
md: 768px Tablets Multi-column layouts, sidebars
lg: 1024px Laptops, small desktops Full navigation, complex grids
xl: 1280px Desktops Maximum content width, 4+ columns
2xl: 1536px Large desktops Maximum layout width

Breakpoint System

Default Configuration

// tailwind.config.js
theme: {
    screens: {
        'sm': '640px',
        'md': '768px',
        'lg': '1024px',
        'xl': '1280px',
        '2xl': '1536px',
    }
}

Usage Pattern

Always write mobile styles first (no prefix), then enhance:

// ✅ CORRECT: Mobile-first
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">

// ❌ WRONG: Desktop-first thinking
<div class="grid grid-cols-3 md:grid-cols-2 lg:grid-cols-1 gap-4">

Core Patterns

1. Responsive Grids

The most common pattern for responsive layouts:

// Single column mobile, 2 columns tablet, 3-4 columns desktop
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">

Examples by Use Case

Card Grids (components/api/card.md:189):

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
    // Card components
</div>

Feature Sections (comprehensive_demo.rs:156):

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
    // Feature items
</div>

Dashboard Layouts (dashboard-demo/src/lib.rs:195):

<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
    // Stats cards
</div>

2. Responsive Spacing

Adjust padding and margins based on screen size:

// Minimal padding on mobile, more on larger screens
<div class="px-4 sm:px-6 lg:px-8">

// Spacing between sections
<div class="space-y-4 md:space-y-6 lg:space-y-8">

3. Responsive Typography

Scale text size appropriately:

// Headings
<h1 class="text-2xl md:text-4xl lg:text-5xl font-bold">
<h2 class="text-xl md:text-2xl lg:text-3xl font-semibold">

// Body text
<p class="text-sm md:text-base lg:text-lg">

4. Hiding/Showing Content

Conditionally display elements by screen size:

// Mobile-only elements
<div class="block md:hidden lg:hidden">

// Tablet and desktop only
<div class="hidden md:block">

// Desktop only
<div class="hidden lg:block">

5. Responsive Dialogs/Modals

Size dialogs appropriately for the viewport:

// Mobile: full width, Desktop: constrained width
<DialogContent class="sm:max-w-[425px]">

Component-Specific Guidelines

Navigation Components

Navigation Menu

  • Mobile: Collapsed into hamburger menu
  • Tablet+: Full horizontal navigation
// Mobile menu trigger
<button class="md:hidden" aria-label="Open menu">

// Desktop navigation
<nav class="hidden md:flex">

Breadcrumb

  • Mobile: Truncate with ellipsis, show last item
  • Tablet+: Show full path
<div class="flex items-center space-x-2 text-sm">
    <BreadcrumbItem class="hidden sm:inline-block">Home</BreadcrumbItem>
    <BreadcrumbItem class="hidden sm:inline-block">Products</BreadcrumbItem>
    <BreadcrumbItem>Current Page</BreadcrumbItem>
</div>

Form Components

Forms

  • Mobile: Single column, stacked labels
  • Tablet+: Two-column grids for related fields
  • Desktop: Can use 3-4 columns for complex forms
<form class="space-y-4 md:space-y-6">
    // Single column on mobile
    <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
        <FormField>
            <FormLabel>First Name</FormLabel>
            <Input />
        </FormField>
        <FormField>
            <FormLabel>Last Name</FormLabel>
            <Input />
        </FormField>
    </div>
</form>

Input Fields

  • Minimum width: Never constrain on mobile
  • Maximum width: Constrain on desktop for readability
<Input class="w-full md:max-w-md" />

Select/Dropdown

  • Mobile: Native select or full-screen picker
  • Desktop: Dropdown with constrained width

Layout Components

Cards

  • Mobile: Full width, stacked content
  • Tablet+: Grid layouts (2-3 columns)
  • Desktop: 4 columns maximum
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
    <Card>
        <CardHeader>
            <CardTitle>Title</CardTitle>
        </CardHeader>
        <CardContent>Content</CardContent>
    </Card>
    // More cards...
</div>

Sheet/Drawer

  • Mobile: Full width from bottom
  • Tablet+: Fixed width from side
<Sheet>
    <SheetContent class="w-full sm:max-w-sm">
        // Content
    </SheetContent>
</Sheet>

Dialog

  • Mobile: Full viewport with margins
  • Tablet+: Constrained max-width
<DialogContent class="sm:max-w-[425px]">
    // Dialog content
</DialogContent>

Data Display

Tables

  • Mobile: Stack rows or enable horizontal scroll
  • Tablet+: Standard table layout
// Mobile: Card-based rows
<div class="md:hidden">
    // Repeated card layout for each row
</div>

// Desktop: Standard table
<div class="hidden md:block overflow-x-auto">
    <Table>
        // Table content
    </Table>
</div>

Tabs

  • Mobile: Scrollable horizontal tabs
  • Desktop: Full-width tabs
<TabsList class="w-full justify-start overflow-x-auto">
    <TabsTrigger>Tab 1</TabsTrigger>
    <TabsTrigger>Tab 2</TabsTrigger>
</TabsList>

Feedback Components

Alerts/Toasts

  • Mobile: Full width at bottom/top
  • Desktop: Constrained width, positioned
<Alert class="mx-4 md:mx-0 md:max-w-md">
    <AlertDescription>Message</AlertDescription>
</Alert>

Progress Indicators

  • Mobile: Full width
  • Desktop: Constrained width or centered
<Progress class="w-full md:w-96" value={progress} />

Layout Patterns

Container Pattern

Center content with responsive max-width:

<div class="container mx-auto px-4 sm:px-6 lg:px-8">
    // Content
</div>

Sidebar Layout

Responsive sidebar that collapses on mobile:

<div class="flex flex-col md:flex-row">
    <aside class="w-full md:w-64 mb-4 md:mb-0 md:mr-6">
        // Sidebar content
    </aside>
    <main class="flex-1">
        // Main content
    </main>
</div>

Stacked to Side-by-Side

Move from stacked layout to side-by-side:

<div class="flex flex-col lg:flex-row gap-6">
    <div class="flex-1">
        // Primary content
    </div>
    <div class="w-full lg:w-80">
        // Secondary content
    </div>
</div>

Typography

Responsive Font Sizes

// Display headings
<h1 class="text-3xl md:text-5xl lg:text-7xl font-bold">

// Section headings
<h2 class="text-2xl md:text-3xl lg:text-4xl font-bold">

// Subsection headings
<h3 class="text-xl md:text-2xl font-semibold">

// Body text
<p class="text-sm md:text-base">

// Small text
<small class="text-xs md:text-sm">

Line Height

Maintain readability across devices:

// Headings: tighter
<h1 class="leading-tight">

// Body: comfortable
<p class="leading-relaxed md:leading-normal">

Text Truncation

Handle long text on mobile:

// Truncate with ellipsis
<p class="truncate">Long text that needs truncation</p>

// Line clamping
<p class="line-clamp-2 md:line-clamp-3">
    Long content that should be limited to specific lines
</p>

Spacing

Spacing Scale

Use consistent spacing units:

Mobile Tablet Desktop Use Case
p-2 p-3 p-4 Tight spacing
p-4 p-6 p-8 Comfortable spacing
gap-2 gap-4 gap-6 Grid gaps

Responsive Margins

// Section margins
<section class="my-8 md:my-12 lg:my-16">

// Element spacing
<div class="space-y-4 md:space-y-6 lg:space-y-8">

Padding

// Container padding
<div class="p-4 sm:p-6 lg:p-8">

// Card padding
<Card class="p-4 md:p-6">

Touch Targets

Minimum Sizes

Ensure all interactive elements meet minimum touch targets:

  • Minimum size: 44x44 pixels (iOS) / 48x48 pixels (Android)
  • Recommended: 48x48 pixels for better accessibility

Button Sizing

// Default buttons meet requirements
<Button class="h-10 px-4">Click</Button> // 40px height - acceptable

// Better: Larger touch targets
<Button class="h-12 px-6 md:h-10 md:px-4">Click</Button> // 48px on mobile

Icon Buttons

// Add padding to meet minimum
<IconButton class="p-3 md:p-2">
    <Icon class="w-6 h-6" />
</IconButton>

Spacing Between Targets

Maintain minimum 8px between touch targets:

<div class="flex gap-4 md:gap-2">
    <Button>Button 1</Button>
    <Button>Button 2</Button>
</div>

Performance Considerations

Image Optimization

// Use responsive images
<img
    class="w-full h-auto"
    src="image-small.jpg"
    srcset="image-small.jpg 640w, image-medium.jpg 1024w, image-large.jpg 1536w"
    sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
/>

// Or use aspect ratio containers
<AspectRatio ratio={16 / 9}>
    <img src="image.jpg" class="object-cover w-full h-full" />
</AspectRatio>

Lazy Loading

// Load components only when needed
<Show when=move || is_large_screen()>
    <DesktopOnlyComponent />
</Show>

CSS Optimization

// Prefer utility classes over inline styles
<div class="flex flex-col md:flex-row"> // ✅ Good

// Avoid heavy computations
<div style={move || format!("width: {}px", width.get())}> // ❌ Bad

Reduce Bundle Size

// Only import what you need
use leptos_shadcn_ui::{Button, Input}; // ✅ Good

use leptos_shadcn_ui::prelude::*; // ❌ Bad - tree-shaking limited

Testing Responsive Designs

Browser DevTools

  1. Open DevTools (F12)
  2. Toggle device toolbar (Ctrl+Shift+M / Cmd+Shift+M)
  3. Test common breakpoints:
    • 375px (iPhone SE)
    • 390px (iPhone 12/13)
    • 768px (iPad)
    • 1024px (iPad Pro, small laptop)
    • 1440px (desktop)

Automated Testing

Use Playwright for responsive testing:

// tests/e2e/responsive.spec.ts
test.describe('Responsive Design', () => {
    const sizes = [
        { width: 375, height: 667 },  // Mobile
        { width: 768, height: 1024 }, // Tablet
        { width: 1440, height: 900 }, // Desktop
    ];

    sizes.forEach(size => {
        test(`Layout at ${size.width}px`, async ({ page }) => {
            await page.setViewportSize(size);
            await page.goto('/');

            // Test mobile-specific behavior
            if (size.width < 768) {
                await expect(page.locator('.mobile-menu')).toBeVisible();
            } else {
                await expect(page.locator('.desktop-nav')).toBeVisible();
            }
        });
    });
});

Accessibility Testing

Ensure touch targets are accessible:

# Use axe DevTools
npm run test:a11y

# Manual checklist
- [ ] All buttons >= 44x44px on mobile
- [ ] Touch targets have 8px spacing
- [ ] No horizontal scrolling on mobile (except intentional)
- [ ] Text is readable without zooming
- [ ] Forms are easy to complete on mobile

Examples

Complete Mobile-First Component

use leptos::*;
use leptos_shadcn_ui::*;

#[component]
pub fn ResponsiveDashboard() -> impl IntoView {
    view! {
        <div class="min-h-screen bg-background">
            // Responsive header
            <header class="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur">
                <div class="container flex h-16 items-center px-4 sm:px-6 lg:px-8">
                    <div class="flex flex-1 items-center justify-between">
                        <Logo class="h-8 w-auto" />

                        // Desktop navigation
                        <nav class="hidden md:flex items-center space-x-6">
                            <a href="/" class="text-sm font-medium">Home</a>
                            <a href="/dashboard" class="text-sm font-medium">Dashboard</a>
                            <a href="/settings" class="text-sm font-medium">Settings</a>
                        </nav>

                        // Mobile menu trigger
                        <button class="md:hidden p-2" aria-label="Open menu">
                            <MenuIcon class="h-6 w-6" />
                        </button>
                    </div>
                </div>
            </header>

            // Main content area
            <main class="container px-4 py-6 sm:px-6 lg:px-8">
                // Page header
                <div class="mb-8">
                    <h1 class="text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl">
                        Dashboard
                    </h1>
                    <p class="mt-2 text-sm text-muted-foreground sm:text-base">
                        Welcome to your dashboard
                    </p>
                </div>

                // Stats grid - responsive columns
                <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-8">
                    <Card class="p-6">
                        <CardHeader class="pb-2">
                            <CardTitle class="text-sm font-medium">Total Users</CardTitle>
                        </CardHeader>
                        <CardContent>
                            <div class="text-2xl font-bold">1,234</div>
                            <p class="text-xs text-muted-foreground">+12% from last month</p>
                        </CardContent>
                    </Card>
                    // More stat cards...
                </div>

                // Main content - stacked on mobile, side-by-side on desktop
                <div class="grid gap-6 lg:grid-cols-7">
                    // Primary content
                    <div class="lg:col-span-5 space-y-6">
                        <Card class="p-4 sm:p-6">
                            <CardHeader>
                                <CardTitle class="text-lg">Recent Activity</CardTitle>
                            </CardHeader>
                            <CardContent>
                                <div class="space-y-4">
                                    // Activity items
                                </div>
                            </CardContent>
                        </Card>
                    </div>

                    // Sidebar content
                    <div class="lg:col-span-2 space-y-6">
                        <Card class="p-4 sm:p-6">
                            <CardHeader>
                                <CardTitle class="text-lg">Quick Actions</CardTitle>
                            </CardHeader>
                            <CardContent class="space-y-2">
                                <Button class="w-full justify-start" size="sm">
                                    Create New
                                </Button>
                                <Button class="w-full justify-start" variant="outline" size="sm">
                                    Import Data
                                </Button>
                            </CardContent>
                        </Card>
                    </div>
                </div>
            </main>
        </div>
    }
}

Responsive Form Example

#[component]
pub fn ResponsiveForm() -> impl IntoView {
    view! {
        <form class="space-y-6">
            <div>
                <h2 class="text-xl font-semibold mb-4 sm:text-2xl">User Information</h2>

                // Name fields - side-by-side on tablet and up
                <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
                    <FormField>
                        <FormLabel>First Name</FormLabel>
                        <Input class="w-full" placeholder="John" />
                    </FormField>

                    <FormField>
                        <FormLabel>Last Name</FormLabel>
                        <Input class="w-full" placeholder="Doe" />
                    </FormField>
                </div>
            </div>

            // Contact section - responsive grid
            <div>
                <h3 class="text-lg font-semibold mb-4">Contact Information</h3>

                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <FormField>
                        <FormLabel>Email</FormLabel>
                        <Input type="email" class="w-full" placeholder="john@example.com" />
                    </FormField>

                    <FormField>
                        <FormLabel>Phone</FormLabel>
                        <Input type="tel" class="w-full" placeholder="+1 234 567 8900" />
                    </FormField>

                    <FormField class="md:col-span-2">
                        <FormLabel>Address</FormLabel>
                        <Input class="w-full" placeholder="123 Main St" />
                    </FormField>

                    <FormField>
                        <FormLabel>City</FormLabel>
                        <Input class="w-full" placeholder="New York" />
                    </FormField>

                    <FormField>
                        <FormLabel>State</FormLabel>
                        <Input class="w-full" placeholder="NY" />
                    </FormField>
                </div>
            </div>

            // Form actions - stacked on mobile, side-by-side on desktop
            <div class="flex flex-col-reverse sm:flex-row gap-3 sm:justify-end">
                <Button variant="outline" class="w-full sm:w-auto">
                    Cancel
                </Button>
                <Button class="w-full sm:w-auto">
                    Save Changes
                </Button>
            </div>
        </form>
    }
}

Checklist for Mobile-First Development

Use this checklist when creating or modifying components:

Layout

  • Starts with single-column layout for mobile
  • Uses responsive breakpoints correctly (md: not md:)
  • Content doesn't overflow horizontally
  • Vertical scrolling works naturally

Typography

  • Base font size is readable (16px minimum)
  • Headings scale appropriately
  • Line height is comfortable
  • Text truncation handles long content

Spacing

  • Padding is appropriate for mobile (not too tight)
  • Touch targets meet minimum size (44x44px)
  • Spacing between interactive elements (8px minimum)

Navigation

  • Menu is accessible on mobile
  • Breadcrumbs don't break layout
  • Back/buttons are easily accessible

Forms

  • Input fields are full width on mobile
  • Labels are positioned correctly
  • Error messages are visible
  • Submit buttons are prominent

Performance

  • Images are optimized
  • No unnecessary re-renders
  • Bundle size is minimized
  • Lazy loading used appropriately

Accessibility

  • All interactive elements are keyboard accessible
  • Touch targets meet WCAG guidelines
  • Color contrast meets AA standards
  • Screen reader announces content correctly

Resources


Last updated: January 2026