Skip to content

Server Components

In a React Server Components app — the Next.js App Router — a module is a server component unless it starts with 'use client'. The packages declare it on every module that needs the browser: one that holds state, uses a context or an effect, or renders one that does (decision 16). Everything else stays on the server.

Runs asComponents
ServerAlert, Badge, Breadcrumb, Button, Card, Chip, Grid, Heading, Input, Kbd, Link, Meter, Pagination, Paragraph, Progress, Select, Separator, Skeleton, Slider, Spinner, Stack, Table, Text, Textarea, and the fieldset classes
ClientAccordion, Avatar, Calendar, RangeCalendar, Checkbox, Combobox, DatePicker, DateRangePicker, Dialog, Drawer, Field, HoverCard, Menu, Menubar, OtpInput, Popover, RadioGroup, RangeSlider, Stepper, Switch, Tabs, Toast, Toggle, ToggleGroup, Toolbar, Tooltip, TreeView

A server component renders to HTML and sends no JavaScript of its own: a page of Cards, Headings and Links ships none for them. Server is not static, though — Input, Select and Textarea are native elements, so they take input and submit with a form like any other.

A client component hydrates: its JavaScript loads, and it runs in the browser. The @arun-dev/headless components are all client components, apart from Button, and useRender and mergeProps can be called from server code.

Every component, server or client, can be rendered from a server component. What crosses from the server to a client component has to be data — strings, numbers, arrays, objects, JSX — not functions. So:

From a server componentWorks?
Any server component, with any props✅
A client component, uncontrolled: defaultValue, defaultOpen, name✅
Namespaced parts: <Dialog.Root>, <Tabs.Tab>✅
A callback: onValueChange, onOpenChange, onClick❌ — move it to a client component
Controlled state: value with onValueChange, open with onOpenChange❌ — move it to a client component
A function as children or a prop: Combobox.List’s {(item) => …}, getDayInfo❌ — move it to a client component
useToastManager()❌ — a hook: a client component
// app/settings/page.tsx — a server component: no 'use client', and none needed.
import { Card, Heading, Tabs } from '@arun-dev/ui';
export default function SettingsPage() {
return (
<Card>
<Heading level={1}>Settings</Heading>
<Tabs.Root defaultValue="profile">
<Tabs.List aria-label="Settings">
<Tabs.Tab value="profile">Profile</Tabs.Tab>
<Tabs.Tab value="billing">Billing</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="profile">…</Tabs.Panel>
<Tabs.Panel value="billing">…</Tabs.Panel>
</Tabs.Root>
</Card>
);
}

When a component needs a callback or controlled state, put that part in a client component of your own, and keep the page on the server:

app/settings/PlanPicker.tsx
'use client';
import { useState } from 'react';
import { RadioGroup } from '@arun-dev/ui';
export function PlanPicker() {
const [plan, setPlan] = useState<string | null>('free');
return (
<RadioGroup.Root aria-label="Plan" value={plan} onValueChange={setPlan}>
{/* … */}
</RadioGroup.Root>
);
}

Forms without a client component

Input, Select, Textarea, Slider, and the uncontrolled Checkbox, RadioGroup, Switch and DatePicker all submit their values natively. A form of them posted to a Server Action needs no 'use client' of your own.

The docs app’s server-components spec loads every entry of both packages on React’s server build and reads every namespace member, and it pins the list of server components above. A module that starts using client-only React without 'use client', or a component that moves between the two lists, fails it.