next-yak

repository·main·Indexed 20 days ago

https://github.com/digitecgalaxus/next-yak

A performant CSS-in-JS solution featuring build-time evaluation and static folding. It includes the eslint-plugin-yak for migration from styled-components and best practice enforcement, as well as integrations for Next.js via the withYak wrapper and Vite via the viteYak plugin.

Tokens
41.4K
Snippets
155
Records
195
Agent score
68%

What's inside next-yak

  1. Supported frameworks and bundlers

    main

    next-yak provides first-class support for the following environments:

    • Next.js: Supports both Webpack and Turbopack.
    • Vite: Supports Vite 7+ (including Vite 8 with OXC/Rolldown) since v9.1.0. This includes any Vite-based framework like react-router or tanstack start.
    • Storybook: Supports Storybook 10+ with both Vite and Webpack builders via the storybook-addon-yak addon.
  2. How the next-yak runtime works

    main

    Despite being marketed as "zero-runtime", next-yak includes a small, side-effect-free runtime used for handling dynamic styles that cannot be folded at compile time.

    It acts as a type-safe classnames utility that evaluates dynamic functions (conditionals and CSS variable values) during render. It returns:

    1. A className string: A merged result of incoming className props, base classes, and conditional classes.
    2. A style object: Contains CSS variable values derived from props.

    Because it has no side effects (it does not inject <style> or <link> elements), it is compatible with both React Server Components (RSC) and Client Components.

    function __yak_button(className, ...dynamicParts) {
      // returns a React component
      return (props) => {
        const classNames = new ClassNames(props.className);
        classNames.add(className);
        const style = {};
    
        for (const part of dynamicParts) {
          if (typeof part === "function") {
            // conditional css block, e.g.:
            // (props) => props.$primary && css("Button_primary")
            const result = part(props);
            if (result) result(props, classNames, style);
          } else if (part?.style) {
            // css variable values, e.g.:
            // { style: { "--color": (props) => props.$color } }
            for (const [key, value] of Object.entries(part.style)) {
              style[key] = typeof value === "function" ? value(props) : value;
            }
          }
        }
    
        return <button className={classNames.value} style={style} />;
      };
    }
  3. Create dynamic styles using props

    main

    Next-yak supports dynamic styling by allowing you to use props within your style templates.

    • CSS Templates: When you use the css function or return a template string that generates new CSS rules, next-yak creates a new CSS class and applies it at runtime.
    • CSS Variables: When a function returns a direct value (like a color or dimension) without creating a new CSS block, next-yak automatically converts it into a CSS custom property (variable) and sets it on the element's style attribute to ensure high performance.
    import { css, styled } from 'next-yak';
    
    const Paragraph = styled.p<{ $primary?: boolean }>`
      background: ${props => props.$primary ? "#BF4F74" : "white"};
    ${props => props.$primary ?
    css`       color: white;
        ` : css`       color: #BF4F74
        `};
    font-size: 2rem;
    font-weight: bold;
    `;
    
    const Component = () => {
      return (
        <>
          <Paragraph $primary>Hello there primary!</Paragraph>
          <Paragraph>Hello there non-primary!</Paragraph>
        </>
      );
    }
  4. Compare Next-Yak with other styling solutions

    main

    Next-Yak is evaluated against other styling approaches based on several performance and developer experience metrics. When choosing a styling solution, consider the following trade-offs:

    • Input Delay: Injecting styles during streaming or render can cause long delays on low-end devices due to style invalidations.
    • Zero Runtime: The absence of code for CSSOM insertions or updates.
    • Zero SSR Overhead: The absence of code generating CSS during Server-Side Rendering.
    • Compile time optimization: Minimizing compile time (e.g., by using Rust).
    • Ecosystem Support: Next-Yak supports Vite (since v9.1.0) and Storybook (via storybook-addon-yak).
  5. How next-yak works: Core Architecture

    main

    The next-yak architecture is divided into three main parts that work together to provide a zero-runtime-overhead (mostly) styling experience:

    1. The Compile Time Part: Consists of a Rust-based SWC plugin and a bundler-specific loader. The SWC plugin extracts styles from tagged template literals (like styled and css) and transforms code to use next-yak runtime functions. The loader then resolves dependencies and delivers the extracted CSS through the bundler's native pipeline.
    2. The Runtime Part: A minimal set of functions responsible for merging class names and handling dynamic styles (like CSS variables) at runtime.
    3. Optional Context: A theme provider for managing context-based styling.

    next-yak supports three bundler backends: webpack (Next.js), Turbopack (Next.js), and Vite.

  6. Understand why styled components are missing from React DevTools

    main

    By default, next-yak performs folding (controlled by the foldStatic option). This optimizes your JSX at build time by inlining the styled component directly into the HTML element.

    For example, <Button> is compiled into a plain <button> with a generated className. Because the component is inlined, it no longer exists in the React tree, meaning it will not appear in React DevTools or component stacks. This behavior is consistent in both development and production.

  7. How benchmarks are structured

    main

    Benchmarks in this project are organized into three distinct layers to ensure both performance accuracy and visual correctness:

    1. Source Generation (bench/<case>/gen.ts): Generates a TSX source string for both libraries. For next-yak, it also runs the source through the SWC plugin to produce a .compiled.tsx file. This mimics the withYak loader behavior used during app builds.
    2. Performance Suite (bench/index.bench.tsx): Imports the .compiled.tsx (yak) and .tsx (sc) outputs. It registers them as Benchmark.Suite cases and pairs the variants into the final HTML results table.
    3. Visual Demo (app/bench/[slug]/page.tsx): Imports the non-compiled .tsx outputs. This layer exercises the real withYak webpack loader path in a real Next.js environment, allowing developers to verify that the benchmarked workloads match what would actually ship to a production app.
  8. Core concepts of next-yak

    main

    next-yak is a zero-runtime CSS-in-JS library designed for high performance and compatibility with React Server Components (RSC). It achieves this through a two-part architecture:

    1. Static Analysis: At build time, next-yak parses your styles and generates standard CSS-Modules files. This allows the bundler's native CSS tool (like PostCSS for Webpack or Lightning CSS for Turbopack/Vite) to optimize the CSS.
    2. Runtime: To support dynamic styling (e.g., styles based on props), a lightweight runtime is used. It manages generated class names and modifies them based on the provided props at runtime.

    This approach provides the benefits of static CSS (no processing during hydration, support for 103 early hints) while maintaining the developer experience of dynamic CSS-in-JS.

  9. Use build-time constants with .yak files

    main

    In Vite, you can use .yak.ts or .yak.tsx files to run TypeScript/JavaScript at build time to generate design tokens or expand data. These files are evaluated in a sandboxed context, and the resulting values are injected into your CSS.

    Note: For simple string constants, use standard .ts modules. Use .yak files only when you need build-time logic (loops, calculations, etc.) to generate styles.

    // Base scale for spacing (in px)
    const baseSpacing = [0, 4, 8, 12, 16, 24, 32, 40, 48];
    
    export const spacing = Object.fromEntries(
      baseSpacing.map((value, index) => [`s${index}`, `${value}px`]),
    );
    
    // Generate a simple color ramp
    const baseHue = 220;
    
    export const colors = Array.from({ length: 9 }, (_, i) => {
      const lightness = 30 + i * 5;
      return `hsl(${baseHue}, 80%, ${lightness}%)`;
    });
    /** @jsxImportSource next-yak */
    import { styled } from "next-yak";
    import { spacing, colors } from "./theme/tokens.yak";
    
    const Card = styled.div`
      padding: ${spacing.s3};
      background-color: ${colors[4]};
      border-radius: ${spacing.s2};
    `;
  10. Create reusable CSS Mixins

    main

    Mixins are declarations of the css utility function that can be stored in variables and reused within styled declarations. They can be static or dynamic (accepting props).

    Static Mixin:

    const mixin = css`color: green;`;
    const MyComp = styled.div`background-color: yellow; ${mixin}`;

    Dynamic Mixin (using props):

    const mixin = css`color: ${(props) => (props.$green ? "green" : "blue")};`;
    const MyComp = styled.div`background-color: yellow; ${mixin}`;

    During build time, css literals are converted into class names or CSS variables to optimize performance.

    import { css, styled } from 'next-yak';
    
    const mixin = css`
      color: ${(props) => (props.$green ? "green" : "blue")};
    `;
    
    const MyComp = styled.div`
      background-color: yellow;
      ${mixin}
    `;
  11. Understand yak/style-conditions rule logic

    main

    The yak/style-conditions rule warns when arrow functions inside styled/css literals return static values that would result in unnecessary or invalid CSS variables.

    next-yak uses two primary methods for dynamic styling:

    1. Class-based Dynamic Styles: Used for binary conditions or fixed variations. Styles are pre-compiled into separate CSS classes that are toggled at runtime. This is highly efficient.
    2. CSS Variables: Used for truly dynamic values (e.g., user inputs, animation states, calculated positions). Values are extracted into CSS custom properties and set via inline styles at runtime.

    The rule helps you avoid using the CSS variable approach for values that could have been handled more efficiently via the class-based approach.

  12. Use Automatic CSS Variables for dynamic properties

    main

    When you use a function inside a styled template literal to return a value for an existing property, next-yak automatically transforms these into CSS variables during build time. This ensures dynamic values are handled efficiently via the style attribute without interfering with other CSS variables.

    Example:

    const Box = styled.div<{ $variant: "primary" | "secondary", $color: string }>`
      font-size: ${props => props.$variant === "primary" ? "2rem" : "1rem" };
      color: ${props => props.$color};
      display: flex;
    `;

    This is transformed into a component that uses var(--var1) and var(--var2) internally.

    import { styled } from 'next-yak';
    
    const Box = styled.div<{ $variant: "primary" | "secondary", $color: string }>`
      font-size: ${props => props.$variant === "primary" ? "2rem" : "1rem" };
      color: ${props => props.$color};
      display: flex;
    `;