Centralize breakpoint logic when migrating to Tailwind
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 26
- Avg merge
- 11h 58m
- Merged PRs (30d)
- 16
Description
## Context
The current design system uses [Stitches](https://stitches.dev/) which was [sunset in 2023](https://github.com/stitchesjs/stitches/discussions/1149) 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](https://tailwindcss.com/docs/upgrade-guide#theme-values-in-javascript) is to read them from CSS rather than maintaining a separate JS config:
```ts
// 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
```ts
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):
```css
/* 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the Tailwind v4 theme in app.css and the breakpoint usage in components/BottomDrawer/index.tsx, layouts/account.tsx, layouts/main.tsx, components/DelegatingWidget/Input.tsx, pages/voting/[poll].tsx, and pages/treasury/[proposal].tsx. Centralize the breakpoint source for CSS and JS, update each listed usage, and verify that no mismatched hardcoded breakpoint remains, including the 768px case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- tailwindcss, typescript
- Domain
- frontend
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100