Files
teatea-pension/.trellis/spec/frontend/css-layout.md

7.9 KiB

CSS & Layout Best Practices

This document covers CSS patterns, layout strategies, and cross-environment compatibility considerations.

Flexbox Patterns

Use items-stretch on Main Flex Containers

For full-height layouts where children should fill the available space:

// Good: items-stretch (default) allows children to fill height
<div className="flex h-screen">
  <aside className="w-64 bg-gray-100">
    {/* Sidebar fills full height */}
  </aside>
  <main className="flex-1">
    {/* Main content fills full height */}
  </main>
</div>
// Bad: items-center prevents children from filling container height
<div className="flex h-screen items-center">
  <aside className="w-64 bg-gray-100">
    {/* Sidebar only as tall as its content */}
  </aside>
  <main className="flex-1">
    {/* Main content only as tall as its content */}
  </main>
</div>

Nested Flex Containers

<div className="flex h-screen flex-col">
  {/* Header - fixed height */}
  <header className="h-16 shrink-0 border-b">
    <nav>...</nav>
  </header>

  {/* Main area - fills remaining space */}
  <div className="flex min-h-0 flex-1">
    {/* Sidebar - fixed width, full height */}
    <aside className="w-64 shrink-0 overflow-y-auto border-r">
      <nav>...</nav>
    </aside>

    {/* Content - fills remaining width */}
    <main className="flex-1 overflow-y-auto">
      <div className="p-6">...</div>
    </main>
  </div>
</div>

min-h-0 for Overflow Control

When using flex containers with scrollable children:

// Without min-h-0, content may overflow
<div className="flex h-screen flex-col">
  <div className="flex-1">
    {/* This might overflow if content is tall */}
  </div>
</div>

// With min-h-0, overflow is properly contained
<div className="flex h-screen flex-col">
  <div className="min-h-0 flex-1 overflow-y-auto">
    {/* Content scrolls within container */}
  </div>
</div>

Parent-Child Styling Pattern

Parent Provides External Styles

The parent component controls:

  • Positioning (absolute, relative, grid placement)
  • External spacing (margin, gap)
  • Size constraints (width, max-width)
// Parent component
<div className="grid grid-cols-3 gap-4">
  <Card className="col-span-2" />  {/* Parent sets grid span */}
  <Card />
</div>

Child Provides Internal Layout

The child component controls:

  • Internal padding
  • Internal layout (flex, grid)
  • Background, borders, shadows
  • Typography
// Child component
export function Card({ className, children }: CardProps) {
  return (
    <div
      className={cn(
        // Internal styles owned by Card
        'rounded-lg border bg-white p-4 shadow-sm',
        // External styles from parent
        className
      )}
    >
      {children}
    </div>
  );
}

Complete Example

// Page layout (parent)
export function DashboardPage() {
  return (
    <div className="grid gap-6 p-6 lg:grid-cols-3">
      {/* Parent controls: grid position, external spacing */}
      <StatsCard className="lg:col-span-2" />
      <ActivityFeed className="lg:row-span-2" />
      <RecentOrders />
    </div>
  );
}

// Card component (child)
export function StatsCard({ className }: { className?: string }) {
  return (
    <div
      className={cn(
        // Child controls: internal padding, background, border
        'flex flex-col gap-4 rounded-xl bg-white p-6 shadow',
        className
      )}
    >
      {/* Internal layout */}
    </div>
  );
}

Cross-Environment Testing

Dev Mode (Turbopack) vs Production (Webpack)

CSS may behave differently between development and production builds:

# Test in development (Turbopack)
pnpm dev

# Test in production (Webpack)
pnpm build && pnpm start

Common Differences

  1. CSS Order: Tailwind classes may be applied in different orders
  2. Purging: Unused classes removed in production
  3. Minification: Class names optimized

Testing Checklist

  • Run pnpm dev and test all features
  • Run pnpm build && pnpm start and test again
  • Check for visual differences
  • Verify responsive breakpoints work
  • Test animations and transitions

Mobile Touch Optimization

Disable Tap Highlight

Prevent the default blue/gray highlight on mobile tap:

// Using Tailwind
<button className="[-webkit-tap-highlight-color:transparent]">
  Tap me
</button>

// Using inline styles (when needed)
<button style={{ WebkitTapHighlightColor: 'transparent' }}>
  Tap me
</button>

// Global reset in CSS
@layer base {
  button, a, [role="button"] {
    -webkit-tap-highlight-color: transparent;
  }
}

Touch-Friendly Sizing

// Minimum touch target: 44x44px
<button className="min-h-[44px] min-w-[44px] p-3">
  <Icon size={20} />
</button>

// For lists
<ul className="divide-y">
  {items.map((item) => (
    <li key={item.id}>
      <button className="w-full px-4 py-3 text-left">
        {item.label}
      </button>
    </li>
  ))}
</ul>

Prevent Pull-to-Refresh

When implementing custom scroll behaviors:

<div
  className="h-screen overflow-y-auto overscroll-contain"
  style={{ touchAction: 'pan-y' }}
>
  {/* Scrollable content */}
</div>

Responsive Design Patterns

Mobile-First Approach

// Start with mobile styles, add breakpoints for larger screens
<div className="
  p-4           // Mobile: small padding
  md:p-6        // Tablet: medium padding
  lg:p-8        // Desktop: large padding
">
  <h1 className="
    text-xl       // Mobile: small heading
    md:text-2xl   // Tablet: medium heading
    lg:text-3xl   // Desktop: large heading
  ">
    Title
  </h1>
</div>

Container Queries (Tailwind v4)

// Container-based responsive styles
<div className="@container">
  <div className="
    flex flex-col
    @md:flex-row    // Row layout when container >= md
    @lg:gap-6       // Larger gap when container >= lg
  ">
    {/* Content */}
  </div>
</div>

Hiding/Showing Elements

// Hide on mobile, show on desktop
<div className="hidden lg:block">
  Desktop only content
</div>

// Show on mobile, hide on desktop
<div className="lg:hidden">
  Mobile only content
</div>

Z-Index Management

Establish a Scale

/* In your CSS or Tailwind config */
:root {
  --z-dropdown: 10;
  --z-sticky: 20;
  --z-fixed: 30;
  --z-modal-backdrop: 40;
  --z-modal: 50;
  --z-popover: 60;
  --z-tooltip: 70;
}

Tailwind Config

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      zIndex: {
        dropdown: '10',
        sticky: '20',
        fixed: '30',
        modalBackdrop: '40',
        modal: '50',
        popover: '60',
        tooltip: '70',
      },
    },
  },
};

Usage

<div className="z-modal">Modal content</div>
<div className="z-tooltip">Tooltip</div>

Animation Best Practices

Use CSS Transitions

<button className="
  transition-colors duration-200 ease-out
  hover:bg-primary-dark
">
  Hover me
</button>

Respect Motion Preferences

// Disable animations for users who prefer reduced motion
<div className="
  transition-transform duration-300
  motion-reduce:transition-none
  hover:scale-105
  motion-reduce:hover:scale-100
">
  Animated element
</div>

Hardware Acceleration

// Use transform for smooth animations
<div className="
  translate-x-0 transition-transform
  group-hover:translate-x-2
">
  Slides on hover
</div>

Best Practices Summary

  1. items-stretch: Default for main flex containers
  2. Parent External, Child Internal: Clear separation of concerns
  3. Test Both Modes: Always verify in dev AND production
  4. Touch Optimization: Disable tap highlight, ensure touch targets
  5. Mobile First: Build up from smallest screens
  6. Consistent Z-Index: Use a defined scale
  7. Respect Accessibility: Honor motion preferences