Configuration
Global settings for every Zephora component — wrap your app once with ZephoraConfigProvider and configure locale, overlay z-index layers, the portal container and the headless default.
Setup
import { ZephoraConfigProvider } from "@zephora/react";
export default function App() {
return (
<ZephoraConfigProvider
locale="tr"
zIndex={{ modal: 2000, toast: 2100 }}
appendTo={typeof document !== "undefined" ? document.body : null}
unstyled={false}
>
<YourApp />
</ZephoraConfigProvider>
);
}ZephoraConfigProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
locale | string | "en" | Active locale for component UI strings. Only "en" is embedded; load others from @zephora/theme/locales/* (module or JSON) and register with addLocale(). |
zIndex | { dropdown?, overlay?, modal?, popover?, toast?, tooltip?: number } | — | Overrides the layering of floating elements. Written as --z-index-* CSS variables on the root element. |
appendTo | HTMLElement | null | document.body | Container that Portal-based overlays (Dialog, Select menus, Tooltip…) render into. |
unstyled | boolean | false | Makes every component headless by default for Tailwind-first apps. A per-component unstyled prop still wins. |
Reading the config
import { useZephoraConfig, useLocale } from "@zephora/react";
function MyField() {
const config = useZephoraConfig(); // { locale, zIndex, appendTo, unstyled }
const { t, messages, locale } = useLocale();
return <button aria-label={t("close")} />;
}z-index layers
Components take their stacking order from CSS variables with built-in fallbacks, so both the provider and plain CSS can retune them:
:root {
--z-index-dropdown: 1000;
--z-index-overlay: 1040;
--z-index-modal: 1050;
--z-index-popover: 1060;
--z-index-toast: 1070;
--z-index-tooltip: 1080;
}Dark mode
@zephora/theme ships automatic dark-mode helpers. The useColorScheme hook resolves a scheme (with mode: "system" it follows the OS preference live) and by default applies the resolved theme globally:
import { useColorScheme } from "@zephora/theme";
function App() {
// mode: "light" | "dark" | "system" (default "system")
const { scheme, theme } = useColorScheme({ mode: "system" });
return <span>Active scheme: {scheme}</span>;
}useColorScheme(options) props
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "light" | "dark" | "system" | "system" | "system" follows the OS preference live; "light"/"dark" force a scheme. |
light / dark | Theme | lightTheme / darkTheme | Theme objects used for each scheme. |
apply | boolean | true | Write the resolved theme to document.documentElement via applyTheme. |
→ returns | { scheme: "light" | "dark"; theme: Theme } | — | The resolved scheme and the theme object in effect. |
The lower-level primitives are exported too:
import { getSystemColorScheme, watchSystemColorScheme } from "@zephora/theme";
// Reads the OS color scheme (SSR-safe; defaults to "light").
const scheme = getSystemColorScheme(); // "light" | "dark"
// Subscribes to OS changes. Returns an unsubscribe function.
const stop = watchSystemColorScheme((next) => console.log(next));
stop();FOUC-free SSR
For server rendering, colorSchemeCss(light?, dark?) builds a static stylesheet that switches themes with the OS preference — no flash of the wrong theme, no JavaScript needed before paint. Setting data-zephora-scheme on <html> overrides the media query (wire it to a user toggle):
import { colorSchemeCss } from "@zephora/theme";
// Next.js app router — app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<style dangerouslySetInnerHTML={{ __html: colorSchemeCss() }} />
</head>
<body>{children}</body>
</html>
);
}Slot classNames
Multi-slot components (Dialog, Sheet, Tabs, Card, Popover, Tooltip…) expose a per-slot class contract: the root accepts classNames typed as Partial<Record<<Name>Slot, string>> (slot-name unions like DialogSlot are exported). Slot classes are appended after the module classes and are not gated by unstyled — they work in both styled and unstyled mode; in unstyled mode they are the only classes. classNames.root merges alongside the existing className prop, which keeps targeting the root and is applied last. For compound components, classNames is set once on the root and carried to the parts via context.
Server components
The published @zephora/react bundle ships with a "use client" directive, so you can import components directly inside React Server Component trees (Next.js app router) without adding the directive yourself — every Zephora component is a client component.