Chroma Panel

Search documentation

Find a page or section

Recent colors

View as Markdown

Keep the recent swatch row between page loads.

The picker keeps a row of recently used colors in its footer, and by default that row starts empty on every mount. Persist it when people come back to the same picker often, such as a canvas editor or an admin theme screen, where re-finding a color they used yesterday is the slow part. The list is a plain array of strings, so where you keep it is up to you.

"use client";
 
import { ColorInput } from "chroma-panel";
import "chroma-panel/style.css";
import { useEffect, useState } from "react";
 
const STORAGE_KEY = "chroma-panel:recent";
 
export function BrandColorInput() {
  const [recent, setRecent] = useState<string[]>([]);
 
  useEffect(() => {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (raw === null) return;
    try {
      const parsed: unknown = JSON.parse(raw);
      if (Array.isArray(parsed)) {
        setRecent(parsed.filter((c): c is string => typeof c === "string"));
      }
    } catch {
      window.localStorage.removeItem(STORAGE_KEY);
    }
  }, []);
 
  return (
    <ColorInput
      name="brand"
      defaultValue="#3366cc"
      recentColors={recent}
      onRecentColorsChange={(colors) => {
        setRecent(colors);
        window.localStorage.setItem(STORAGE_KEY, JSON.stringify(colors));
      }}
    />
  );
}

The read happens in an effect, not during render, so the server and the first client render both produce an empty list and nothing mismatches. See server rendering for the general rule.

What goes into the list

The panel adds a color at the same moment onChangeComplete fires: when a change is committed, not while a drag is in progress. It adds it as a six-digit hex string, so opacity is not part of the history even when showAlpha is on.

Before adding, the list is de-duplicated by color rather than by string, so #3366CC and #3366cc are one entry, and then capped at ten. onRecentColorsChange hands you the finished list every time, which is why the callback above can store its argument as-is.

One swatch may be missing

The footer hides the recent swatch that matches the color currently selected, since clicking it would do nothing. The stored list is still complete; it is only the row that looks one short.

Controlled or uncontrolled

recentColors and defaultRecentColors follow the same pattern as value and defaultValue, described in controlled and uncontrolled.

Pass defaultRecentColors to seed the list and let the panel manage it from there. It is read once, when the panel mounts, so a list you read out of storage in an effect arrives too late for it. That is what recentColors is for, and why the example at the top uses it.

<ColorInput defaultRecentColors={["#3366cc", "#cc3366"]} />

Pass recentColors and the panel stops keeping its own copy. It will call onRecentColorsChange with what the new list should be and render whatever you pass back, so forgetting to store the result leaves the row frozen.

Adding a color yourself

If you have swatch buttons of your own outside the panel, pushRecent applies the same rules the panel uses, so both paths produce one consistent history.

import { pushRecent } from "chroma-panel";
 
setRecent((current) => pushRecent(current, "#cc3366"));

The signature is pushRecent(list, hex, limit?), and limit defaults to ten. It returns a new array and does not mutate the one you pass.

Turning the row off

showRecentColors={false} removes it. Reach for that in a one-shot picker, where there is no history worth keeping. The row itself, and the eyedropper beside it, are described on the PanelFooter reference.