docs
    agency-site-kit: Brand layout
    v0.11.1.md

    Brand layout

    AgencySiteKitLayout wraps your site in your agency's brand. It fetches the colours, fonts and radii you set in Sonor and publishes them as --sk-* CSS variables, in light and dark, so your case studies (and the rest of your site) can style themselves from one source. It also hands site-kit's client modules the short-lived token they need.

    Add it to your root layout

    // app/layout.tsx
    import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <AgencySiteKitLayout>{children}</AgencySiteKitLayout>
          </body>
        </html>
      );
    }

    It's an async server component, exported from the root entry and from @sonordev/agency-site-kit/layout. On the server it fetches your brand with getPortfolioBrandConfig (cached for an hour, or until Sonor revalidates the portfolio tag, so a brand edit shows up with your case studies) and mints the client token.

    Props

    PropDefaultWhat it does
    childrenYour page
    apiKeySONOR_API_KEYThe key to fetch the brand with and to mint the client token from
    apiUrlSONOR_API_URL, else https://api.sonor.ioThe API origin
    brandConfigFetched from SonorA Partial<BrandConfig> to use instead of fetching. Missing fields fall back to the defaults.
    themeFollows the visitor's system'light' or 'dark' forces one, by setting data-theme on the wrapper
    classNameA class for the wrapper <div>
    credentialtrueMint a short-lived client token for site-kit's client modules. See below.
    showLlmsTxtFooterLinkfalseRender a hidden footer link to /llms.txt after your content

    What it renders

    In order:

    1. A preconnect and a dns-prefetch hint for the Sonor API's origin.
    2. A <style data-agency-site-kit="brand"> with the CSS variables.
    3. The client token for site-kit's client modules (when credential is on and a key is set). It renders nothing visible and never wraps your content.
    4. A <div data-agency-site-kit="root"> around your content, with data-theme when you pass theme, your className, and inline font-family, color and background-color from the variables.

    The CSS variables

    VariableFrom BrandConfig
    --sk-primary, --sk-primary-rgbprimary
    --sk-secondary, --sk-secondary-rgbsecondary
    --sk-bgbackground
    --sk-bg-elevatedbackgroundElevated
    --sk-surfacesurface
    --sk-surface-hoversurfaceHover
    --sk-bordersurfaceBorder
    --sk-text-primarytextPrimary
    --sk-text-secondarytextSecondary
    --sk-text-tertiarytextTertiary
    --sk-radius-sm, --sk-radius, --sk-radius-lgradius.sm, radius.md, radius.lg
    --sk-font-headingfontHeading
    --sk-fontfontBody

    The -rgb variables hold R, G, B, for colours with transparency:

    .card {
      background: var(--sk-surface);
      border: 1px solid var(--sk-border);
      border-radius: var(--sk-radius-lg);
      box-shadow: 0 12px 40px rgba(var(--sk-primary-rgb), 0.15);
    }

    Light and dark

    The light values go on :root and [data-theme="light"], the dark values on [data-theme="dark"], and a prefers-color-scheme: dark rule applies the dark values to :root unless it's marked data-theme="light". So by default the site follows the visitor's system, and theme (or your own data-theme attribute) pins it.

    Dark mode changes only the colours. Its values come from your brand's darkMode overrides, then the built-in dark defaults (DEFAULT_DARK_MODE). Your primary and secondary carry over unless darkMode sets its own.

    If Sonor can't be reached or has no brand set, the layout uses the built-in light defaults (DEFAULT_BRAND_CONFIG) rather than failing the page. Any field your brand leaves out falls back to those defaults too.

    The client token

    site-kit's client modules (analytics, forms, chat, booking) need a credential in the browser, and your SONOR_API_KEY has to stay on the server. So the layout mints a short-lived token from the key on the server and publishes that instead, the same way site-kit's own SiteKitLayout does. That's why you don't need a NEXT_PUBLIC_ copy of your key.

    Mount those modules as siblings of your content, not wrappers around it, so your pages stay server-rendered. See site-kit's layout docs and analytics docs.

    With site-kit's SiteKitLayout

    If your site also mounts SiteKitLayout from @sonordev/site-kit, it publishes its own token. Two publishers would race, so turn this one off:

    // app/layout.tsx
    import { SiteKitLayout } from '@sonordev/site-kit/layout';
    import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <SiteKitLayout>
              <AgencySiteKitLayout credential={false}>{children}</AgencySiteKitLayout>
            </SiteKitLayout>
          </body>
        </html>
      );
    }

    Only set credential={false} when something else publishes the token. Without one, site-kit's client modules can't authenticate.

    Brand utilities

    The root entry exports the pieces the layout is built from, for when you need the CSS somewhere else:

    ExportWhat it does
    generateBrandCSS(config)The full stylesheet above (light, dark and the system-preference rule) as a string. Missing fields fall back to the defaults.
    mergeBrandConfig(partial)A complete BrandConfig from a partial one, over the defaults
    hexToRgb(hex)'#6366f1' to '99, 102, 241'. Takes 3 or 6 hex digits, returns null for anything else.
    DEFAULT_BRAND_CONFIGThe light defaults: an indigo primary, white background, Inter, radii of 0.375rem, 0.5rem and 0.75rem
    DEFAULT_DARK_MODEThe dark colour defaults
    import { generateBrandCSS } from '@sonordev/agency-site-kit';
    import { getPortfolioBrandConfig } from '@sonordev/agency-site-kit/portfolio/server';
    
    const css = generateBrandCSS(await getPortfolioBrandConfig());

    That's the same fetch the layout makes, from the same cache entry, so the two always agree, including on the DEFAULT_BRAND_CONFIG fallback. See Fetching.

    showLlmsTxtFooterLink renders a visually hidden <footer> with a link to /llms.txt after your content. The better way to point AI crawlers at your llms.txt is a response header: see site-kit's llms.txt docs.