# Next.js

> Add the React color picker to a Next.js app, App Router or Pages Router, with the stylesheet import, the use client rule and a full page example.

Source: https://chroma-panel.jscrate.dev/react/frameworks/next-js
Last updated: 2026-09-18

chroma-panel is a React color picker for Next.js that needs no wrapper and no
configuration. Every file in the package that touches the browser already
carries `"use client"`, so importing it from a server component is fine. This
page sets it up in the App Router and in the Pages Router.

## Install

```bash
npm install chroma-panel
```

There are no other packages to add. `dependencies` is empty, and `react` and
`react-dom` are peer dependencies, so the copies already in your app are the
ones that get used.

## The stylesheet

The panel injects its stylesheet from an effect. That means server-rendered
markup is unstyled until hydration. To avoid that flash, import the stylesheet
yourself and turn injection off:

```tsx
<ColorInput injectStyles={false} />
```

Where the import goes depends on the router, so each section below shows it.
`injectStyles={false}` only stops the `<style>` tag being written; the CSS is in
your bundle either way. If you use Tailwind, import the stylesheet from your CSS
file instead and declare the layer order, as
[styling with Tailwind](https://chroma-panel.jscrate.dev/react/handbook/tailwind) explains.

## Setting up a page

### App Router

Import the stylesheet once in your root layout. The layout stays a server
component.

```tsx
// app/layout.tsx
import "chroma-panel/style.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
```

A server component can render the picker directly, as long as every prop you
pass is serializable:

```tsx
// app/brand/page.tsx
import { ColorInput } from "chroma-panel";

export default function BrandPage() {
  return (
    <main>
      <h2>Brand color</h2>
      <ColorInput defaultValue="#3366cc" injectStyles={false} />
    </main>
  );
}
```

The moment you hold the color yourself, the file becomes a client component.
`useState` and an `onChange` handler are both client-only, so put them behind
your own `"use client"`:

```tsx
// app/brand/brand-picker.tsx
"use client";

import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";

export function BrandPicker() {
  const [color, setColor] = useState<string>("#3366cc");

  const handleChange = (result: ColorChangeResult): void => {
    setColor(result.hex);
  };

  return (
    <>
      <ColorInput value={color} onChange={handleChange} injectStyles={false} />
      <p style={{ color }}>{color}</p>
    </>
  );
}
```

That file is a client component because of your state, not because of
chroma-panel. Which props you own and which the panel keeps is covered in
[controlled and uncontrolled](https://chroma-panel.jscrate.dev/react/handbook/controlled).

### Pages Router

The Pages Router has no server components, so `"use client"` does not apply
anywhere. Next.js only accepts a global stylesheet import in `pages/_app.tsx`,
so that is where the CSS goes:

```tsx
// pages/_app.tsx
import type { AppProps } from "next/app";

import "chroma-panel/style.css";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
```

The page itself is an ordinary React component:

```tsx
// pages/brand.tsx
import { useState } from "react";
import { ColorInput, type ColorChangeResult } from "chroma-panel";

export default function BrandPage() {
  const [color, setColor] = useState<string>("#3366cc");

  const handleChange = (result: ColorChangeResult): void => {
    setColor(result.hex);
  };

  return (
    <main>
      <ColorInput value={color} onChange={handleChange} injectStyles={false} />
    </main>
  );
}
```

## Where "use client" is needed

| File                                       | Needs `"use client"` |
| ------------------------------------------ | -------------------- |
| A server component that renders the picker | No                   |
| A file that holds the color in `useState`  | Yes                  |
| A file that passes `onChange`              | Yes                  |
| Anything in `pages/`                       | Not applicable       |

The rule is about your own code. A server component cannot pass a function
across the boundary, so a handler means a client component.

## No dynamic import needed

You do not need `next/dynamic`, and you do not need `ssr: false`. Nothing is
read from `window` at module scope, `injectStyles` returns early when `document`
is undefined, and the first render writes the color as CSS custom properties, so
the server-rendered markup already shows the right color.

> **Do not reach for ssr: false**
>
> Wrapping the picker in a dynamic import with `ssr: false` only delays it by a
> round trip. It renders on the server correctly as it is.

[Server rendering](https://chroma-panel.jscrate.dev/react/handbook/server-rendering) covers the same ground for
Remix and other SSR setups, and
[entry points](https://chroma-panel.jscrate.dev/react/utils/entry-points) lists the subpaths if you want a
smaller bundle than the default import.
