React Server Components
helixui targets React 18.2 – 19.x, and every component in
@helixui/core is marked 'use client' at the top of its source.
Practical implications below.
TL;DR
| Framework | helixui works | Notes |
|---|---|---|
| Vite + React | ✅ | Default development happy path. |
| Next.js 14 (Pages Router) | ✅ | Drop in. |
| Next.js 14 / 15 (App Router) | ✅ | helixui components automatically opt out of RSC via their 'use client' headers. You can import them from server components without manually adding directives. |
| Remix / React Router v7 | ✅ | Browser runtime; no RSC dance. |
| Astro Islands | ✅ | Use client:load / client:idle / client:visible on the helixui component import. |
| Bun (default) | ✅ | Bun’s JSX runtime is React-compatible. |
| React Server Components (raw) | ⚠️ | helixui is a client component library. You can import helixui components from server components; they hydrate normally. You cannot render them at the server-only boundary. |
| React Native | ❌ | Out of scope — helixui targets the web. |
| Stencil / Lit / Web Components | ❌ | helixui is React-only. |
The “use client” decision
Every component in @helixui/core starts with:
'use client';
import { useState, useRef, ... } from 'react';// ...This is intentional. The whole component surface uses useState,
useRef, useContext, useEffect, or forwardRef — none of which
are RSC-safe. Rather than make consumers wonder which one is which,
we mark them all explicitly.
What this means for you:
- In a Next.js App Router project, you can
import { Button } from '@helixui/core'from a server component file. The component will be rendered on the client; React + Next.js handle the boundary. - You don’t need to mark your file
'use client'just because you import a helixui component. Only mark your file'use client'if your code uses client-only APIs (state, effects, event handlers). - helixui components add a JS payload. If you’re trying to render a
fully-static marketing page, prefer a primitive like
<a>to<Button>where possible. See the “primitives we keep server-safe” section below.
”But I want a server component.”
helixui is a behavior design system, not a primitives design system. The components are interactive by nature (Dialog, Sheet, Popover, Menu, …). RSC versions of these are an open research area in the React community; we’re not going to ship half-broken ones.
That said, a handful of helixui primitives are pure layout/typography and could in principle be server-only:
| Component | Could be server-safe? | Why we still ship as client |
|---|---|---|
Box, Stack, Grid, Flex | ✅ logically | Convenience — one boundary instead of two. |
Text, Heading | ✅ logically | Same. |
Card, Badge, Callout (no JS) | ✅ logically | Same. |
| Everything else | ❌ | Uses hooks. |
If you need a Stack-equivalent in a server component, this two-line version costs nothing:
// app/components/Stack.tsx — server-safe Stackimport type { CSSProperties, ReactNode } from 'react';
export function Stack({ children, gap = 4, ...rest }: { children: ReactNode; gap?: number; style?: CSSProperties;}) { return ( <div style={{ display: 'flex', flexDirection: 'column', gap: `${gap * 4}px`, ...rest.style }}> {children} </div> );}We’ve thought about extracting helixui’s pure-layout primitives into a
separate @helixui/server package. That’s tracked as
RFC 0003 — server primitives split
and we’d welcome a contributor.
Next.js App Router — concrete example
// app/page.tsx — this is a server componentimport { Card, Text, Button } from '@helixui/core';import { fetchUser } from '@/lib/data';
export default async function Page() { const user = await fetchUser(); // server-only fetch is fine return ( <Card> <Text>Welcome, {user.name}!</Text> <Button>Continue</Button> {/* ← rendered on the client, no extra dance */} </Card> );}This compiles and runs. Next.js sees the 'use client' in the helixui
package’s compiled JS and creates the right boundary automatically.
When you do need ‘use client’ in your file
When your code does anything interactive:
'use client'; // ← because YOUR code uses useState
import { useState } from 'react';import { Button } from '@helixui/core';
export function ProfileForm() { const [name, setName] = useState(''); return <Button onClick={() => save(name)}>Save</Button>;}Server Actions (Next 14+)
helixui has no built-in useFormStatus integration yet. Server actions
work with the bare HTML form element; helixui’s <Form> component is a
styled wrapper that passes through, so this works:
'use client';import { Form, TextInput, Button } from '@helixui/core';import { saveProfile } from '@/actions/profile';
export function ProfileForm() { return ( <Form action={saveProfile}> <TextInput name="name" /> <Button type="submit">Save</Button> </Form> );}useFormStatus() works inside the form; we’ll ship a dedicated
useHelixUIFormStatus hook + integration when we land
RFC 0002 — first-class form story.
Astro Islands
---import { Button } from '@helixui/core';---
<Button client:load>Click me</Button>client:visible and client:idle work for non-critical components.
helixui’s bundle size makes client:idle viable even on landing pages.
Edge runtime
helixui’s runtime has no Node-specific APIs. It runs on:
- Vercel Edge
- Cloudflare Workers
- Deno Deploy
- Bun
The lazy-loaded exporters (@helixui/document’s docx, @helixui/slides’
pptxgenjs) require Node — they’re not edge-compatible. Don’t call
exportToDocx/exportToPptx from edge functions; do it from a Node
serverless function or in the browser.
React 19
helixui supports React 19. Ref-as-prop and the new <form action>
patterns work. We have not yet migrated away from forwardRef
internally — that’s a v0.2 task. There is no runtime impact today.
Reporting issues
If you hit “Server Component cannot use useState” or similar errors,
that’s almost always your file missing 'use client', not a helixui bug.
But if you’ve checked that twice and it still breaks, please open an
issue with:
- Framework + version (
[email protected],[email protected], etc.). - The exact error message.
- A 10-line repro.
We treat RSC compatibility as a P0 — Next/Astro are 80% of new helixui adoption.