Skip to content

Centralize breakpoint logic when migrating to Tailwind #579

@rickstaa

Description

@rickstaa

Context

The current design system uses Stitches which was sunset in 2023 and is no longer maintained. Stitches defines CSS-side breakpoints (@bp1@bp4) but has no JS-side media query hooks, which forces us to hardcode pixel values in JS when using useWindowSize().

This has already caused bugs — see #578 where a hardcoded 1020px was misaligned with the @bp3 (1200px) CSS breakpoint, creating a dead zone where buttons were visible but non-functional.

Goal

When migrating to Radix UI + Tailwind CSS, centralize breakpoint logic so CSS and JS always use the same source of truth.

Recommended approach (Tailwind v4)

Tailwind v4 generates CSS variables for all theme values, including breakpoints. The official recommendation is to read them from CSS rather than maintaining a separate JS config:

// Read breakpoint values directly from CSS variables at runtime
const styles = getComputedStyle(document.documentElement);
const lgBreakpoint = styles.getPropertyValue("--breakpoint-lg"); // e.g. "1200px"

This replaces the v3 resolveConfig() approach (which was removed in v4 to reduce bundle size).

Custom hook example

function useBreakpoint(breakpoint: string): boolean {
  const value = getComputedStyle(document.documentElement)
    .getPropertyValue(`--breakpoint-${breakpoint}`);
  const { width } = useWindowSize();
  return width >= parseInt(value, 10);
}

// Usage
const isDesktop = useBreakpoint("lg");

Tailwind theme config

Define breakpoints in CSS (Tailwind v4 style):

/* app.css */
@theme {
  --breakpoint-sm: 520px;  /* current @bp1 */
  --breakpoint-md: 900px;  /* current @bp2 */
  --breakpoint-lg: 1200px; /* current @bp3 */
  --breakpoint-xl: 1800px; /* current @bp4 */
}

These are then available both in Tailwind classes (lg:flex) and in JS via getComputedStyle.

Locations currently using hardcoded breakpoints

File Current usage What to update
components/BottomDrawer/index.tsx width < 1200 !isDesktop or width < lg
layouts/account.tsx width >= 1200 isDesktop or width >= lg
layouts/main.tsx width >= 1200 / width < 1200 isDesktop / !isDesktop
components/DelegatingWidget/Input.tsx width >= 1200 isDesktop
pages/voting/[poll].tsx width > 1200 isDesktop
pages/treasury/[proposal].tsx width >= 768 (isDesktop) Review — 768 doesn't match any current breakpoint

Why move away from Stitches

  • Unmaintained — no updates since 2023, open issues/PRs unaddressed
  • No SSR streaming support — incompatible with React Server Components and Next.js App Router
  • No JS breakpoint hooks — leads to hardcoded pixel values diverging from CSS
  • Ecosystem momentum — Tailwind has broad tooling support, better DX, and active maintenance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions