Skip to content

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

Frameworkhelixui worksNotes
Vite + ReactDefault 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 v7Browser runtime; no RSC dance.
Astro IslandsUse 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 NativeOut of scope — helixui targets the web.
Stencil / Lit / Web Componentshelixui 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:

ComponentCould be server-safe?Why we still ship as client
Box, Stack, Grid, Flex✅ logicallyConvenience — one boundary instead of two.
Text, Heading✅ logicallySame.
Card, Badge, Callout (no JS)✅ logicallySame.
Everything elseUses hooks.

If you need a Stack-equivalent in a server component, this two-line version costs nothing:

// app/components/Stack.tsx — server-safe Stack
import 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 component
import { 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:

app/profile-form.tsx
'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

src/pages/index.astro
---
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/slidespptxgenjs) 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:

We treat RSC compatibility as a P0 — Next/Astro are 80% of new helixui adoption.