Use Krysalicss with Next.js
Add Krysalicss to a Next.js App Router project. Next handles SCSS
natively once the sass package is installed; the framework's
package exports cooperate with both Webpack and Turbopack so no extra
bundler config is needed.
Install
Next.js v15+ compiles SCSS through its built-in Sass loader as long as
the sass package is on disk:
pnpm add sass
pnpm add @adnap/krysalicss No next.config.js changes required.
Wire the SCSS entry
Create one SCSS entry under app/styles/ and @use the framework's
batteries-included entry:
// app/styles/app.scss
@use '@adnap/krysalicss'; Import the SCSS file from the App Router's root layout (app/layout.tsx).
Next emits a single global stylesheet for everything imported here.
// app/layout.tsx
import './styles/app.scss';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>{children}</body>
</html>
);
} The @adnap/krysalicss specifier resolves via the package's sass
export condition to src/krysalicss.scss — no path alias, no
node_modules/ traversal needed.
Override a variable
Configure a per-component import with with () ahead of the framework
entry. Sass picks up the override at the first @use:
// app/styles/app.scss
@use '@adnap/krysalicss/module/card' with (
$padding: 2rem,
$border-radius: 12px,
);
@use '@adnap/krysalicss'; The full override surface is documented in Override component variables.
Strip unused modules
Pass $feature-list to the framework entry. Next bundles only the
emitted slices into the global stylesheet:
// app/styles/app.scss
@use '@adnap/krysalicss' with (
$feature-list: (
base-reset,
base-global,
typography-base,
typography-title,
typography-link,
layout-container,
layout-grid,
element-button,
element-badge,
module-card,
module-navbar,
),
); See Tree-shake to specific modules for the flag inventory and the per-module import alternative.
Light + dark theme switching
The dark theme listens for prefers-color-scheme automatically. For a
manual toggle, set the class on <html> from a server-evaluated inline
script so the first paint is correct (no flash). Next's App Router runs
the layout server-side, so dangerouslySetInnerHTML is appropriate
here:
// app/layout.tsx
import './styles/app.scss';
const themeBootstrap = `
(function () {
try {
var saved = localStorage.getItem('theme');
if (saved === 'dark') document.documentElement.classList.add('theme-dark');
} catch (_) {}
})();
`;
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<script dangerouslySetInnerHTML={{ __html: themeBootstrap }} />
</head>
<body>{children}</body>
</html>
);
} The toggle itself is a tiny Client Component:
// app/components/ThemeToggle.tsx
'use client';
export function ThemeToggle() {
const toggle = () => {
const root = document.documentElement;
root.classList.toggle('theme-dark');
localStorage.setItem(
'theme',
root.classList.contains('theme-dark') ? 'dark' : 'light',
);
};
return <button className="button" onClick={toggle}>Toggle theme</button>;
} The suppressHydrationWarning attribute on <html> tells React not to
warn about the bootstrap script changing the class before hydration.
See also
- Use Krysalicss with Vite: the Vite, Remix, and Astro Vite-powered recipe.
- Use Krysalicss with Astro: integrating into an Astro site's
src/styles/. - Override component variables: the
@use … with ()reference for all overrideable knobs.