skills/doanchienthangdev/omgkit/building-responsive-layouts

building-responsive-layouts

SKILL.md

Building Responsive Layouts

Quick Start

// Responsive grid with auto-fit
export function ResponsiveGrid({ children, minWidth = '300px' }: GridProps) {
  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: `repeat(auto-fit, minmax(min(${minWidth}, 100%), 1fr))`,
      gap: '1.5rem'
    }}>
      {children}
    </div>
  );
}

// With Tailwind
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6">
  {items.map(item => <Card key={item.id} {...item} />)}
</div>

Features

Feature Description Guide
Mobile-First Breakpoints sm(640), md(768), lg(1024), xl(1280), 2xl(1536) ref/breakpoints.md
Fluid Typography clamp() for responsive font sizes ref/fluid-type.md
Container Queries Component-level responsive design ref/container-queries.md
Responsive Images srcset, sizes, and art direction ref/images.md
Touch-Friendly 44px minimum targets, hover vs touch handling ref/touch.md
Safe Areas Handle notched devices and dynamic viewports ref/safe-areas.md

Common Patterns

useMediaQuery Hook

export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const media = window.matchMedia(query);
    setMatches(media.matches);
    const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
    media.addEventListener('change', listener);
    return () => media.removeEventListener('change', listener);
  }, [query]);

  return matches;
}

// Usage
const isMobile = useMediaQuery('(max-width: 639px)');
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');

Container Query Component

.card-container {
  container-type: inline-size;
}

.card {
  display: flex;
  flex-direction: column;
}

@container (min-width: 400px) {
  .card {
    flex-direction: row;
    gap: 1rem;
  }
  .card-image { width: 40%; }
}
export function ResponsiveCard({ image, title, content }: CardProps) {
  return (
    <div className="card-container">
      <article className="card">
        <img src={image} alt="" className="card-image" />
        <div className="card-content">
          <h3>{title}</h3>
          <p>{content}</p>
        </div>
      </article>
    </div>
  );
}

Responsive Navigation

export function ResponsiveNav() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <nav className="relative">
      <div className="flex justify-between items-center h-16 px-4">
        <Logo />
        {/* Desktop nav */}
        <div className="hidden md:flex items-center space-x-8">
          <NavLink href="/">Home</NavLink>
          <NavLink href="/about">About</NavLink>
        </div>
        {/* Mobile menu button */}
        <button
          className="md:hidden p-2 min-h-[44px] min-w-[44px]"
          onClick={() => setIsOpen(!isOpen)}
          aria-expanded={isOpen}
        >
          <span className="sr-only">{isOpen ? 'Close' : 'Open'} menu</span>
          {isOpen ? <XIcon /> : <MenuIcon />}
        </button>
      </div>
      {/* Mobile nav */}
      {isOpen && (
        <div className="md:hidden px-2 pb-3 space-y-1">
          <MobileNavLink href="/" onClick={() => setIsOpen(false)}>Home</MobileNavLink>
          <MobileNavLink href="/about" onClick={() => setIsOpen(false)}>About</MobileNavLink>
        </div>
      )}
    </nav>
  );
}

Best Practices

Do Avoid
Start with mobile-first CSS Hiding essential content on mobile
Use relative units (rem, %, vw) Fixed pixel widths
Test on real devices Relying only on hover states
Use 44px minimum touch targets Small touch targets on mobile
Consider reduced motion preferences Ignoring landscape orientation
Use CSS logical properties (inline, block) Device-specific breakpoints
Handle safe-area-inset for notched devices Assuming mouse input
Weekly Installs
1
GitHub Stars
3
First Seen
6 days ago
Installed on
zencoder1
amp1
cline1
openclaw1
opencode1
cursor1