npm.io
2.1.1 • Published 14h ago

@kilivi-dev/payloadcms-theme-management

Licence
MIT
Version
2.1.1
Deps
3
Size
1.9 MB
Vulns
0
Weekly
0
Stars
9

@kilivi-dev/payloadcms-theme-management

Theme Management plugin for Payload CMS v3 with SSR-ready theme variables, standalone appearance settings, cache revalidation, and Live Preview support.

Note: This package was previously published under @kilivi/payloadcms-theme-management. It is now maintained under the @kilivi-dev scope starting from v1.0.0.

Repository: github.com/vitakili/payload-plugins → packages/theme-management · Changelog · Issues

Version 2.1.0 — Cleaner Appearance Settings UI

New in this release

  • Palette generator sits inside Theme Selection — it is no longer a separate field buried in Color Mode Settings. It renders at the top of the preset column, so you generate a palette from a brand colour or logo and pick or fine-tune a preset directly below it.
  • Collapsible preset list — a summary row shows the active preset with its colour swatches and opens the full scrollable list on click. Collapsed by default, so the live preview stays the focal point.
  • Preset column and live preview always share one row — the layout moved from flex-wrap to CSS grid, so the preview can no longer drop below the preset list; it stacks into a single column below 1024px.
  • Theme Selection is localized — its label and description now follow the active Payload admin language instead of always falling back to English. Consumer-supplied label objects are respected, with en as the fallback.

Version 1.6.0 — Visitor components, accessibility & export

New in this release
  • Visitor componentsColorModeToggle (light/dark/auto with a View Transitions ripple) and ThemeSwitcher (runtime preset switching), imported from @kilivi-dev/payloadcms-theme-management/components/*.
  • Palette generator — build a full light + dark palette from one brand colour or an uploaded logo, right in the admin (generatePaletteFromColor, extractDominantColors).
  • WCAG accessibility audit — live contrast check of the whole palette in both modes, with one-click fixes (auditThemePalette, suggestAccessibleColor).
  • Design token & Tailwind export — W3C Design Tokens JSON + Tailwind v4 @theme inline / v3 config (generateDesignTokens, generateTailwindV4Theme).
  • SSR polish<meta name="theme-color"> and color-scheme emitted by ServerThemeInjector; getThemeHtmlAttributes for one-spread <html> setup.
  • Native Payload i18n — admin strings via the theme-management namespace, extensible/overridable (mergeThemeManagementI18n, i18n plugin option).

See the in-repo Claude skills under .claude/skills/ for task-focused guides.

Version 1.2.0 — Extended Presets & Appearance Controls

New in this release
  • 8 new visual-style theme presets — glassmorphism, claymorphism, neumorphism, aurora, luxury, healthcare, nordic, warm-earth (all in OKLCH)
  • Visual Effects section — effect style (flat/glass/clay/neumorphic/elevated), shadow intensity, backdrop blur, border style/width, glass opacity
  • Hero & Background section — hero style (gradient/mesh/video/image-overlay/solid), height, pattern overlays (9 patterns), section dividers, parallax toggle
  • Component Styles section — button variant (6 options), card style, card hover effects, image style, icon set, navbar style, footer style, scroll/hover animation toggles
  • New TypeScript typesThemeVisualEffects, ThemeHeroBackground, ThemeComponentStyles exported from main package

See CHANGELOG.md for the full list of changes and docs/APPEARANCE_CONTROLS.md for the complete field reference.


Version 1.0.0

Highlights
  • Standard Payload Live Preview flow for client pages (/, /{slug})
  • Optional injected preview endpoint (GET /api/theme/preview)
  • Optional injected revalidation endpoint (POST /api/theme/revalidate)
  • Standalone global mode with automatic theme cache invalidation
  • ThemeTokenSelectField: CSS variable preview swatch resolves computed colors correctly
  • Robust fetching: fallback to standalone global if collection returns no config
  • Professional color picker and extended preset/token support (70+ themes)
  • Full TypeScript support and server/client-safe exports

Installation

pnpm add @kilivi-dev/payloadcms-theme-management
# or
npm i @kilivi-dev/payloadcms-theme-management
# or
yarn add @kilivi-dev/payloadcms-theme-management

Quick Start

1) Register plugin in Payload
A) Inject as tab into existing collection (default)
import { themeManagementPlugin } from '@kilivi-dev/payloadcms-theme-management'
import { buildConfig } from 'payload'

export default buildConfig({
  collections: [
    {
      slug: 'site-settings',
      fields: [{ name: 'siteName', type: 'text' }],
    },
  ],
  plugins: [
    themeManagementPlugin({
      targetCollection: 'site-settings',
      defaultTheme: 'cool',
      livePreview: true,
    }),
  ],
})
B) Create standalone appearance global
import { themeManagementPlugin } from '@kilivi-dev/payloadcms-theme-management'
import { buildConfig } from 'payload'

export default buildConfig({
  plugins: [
    themeManagementPlugin({
      useStandaloneCollection: true,
      standaloneCollectionSlug: 'appearance-settings',
      standaloneCollectionLabel: 'Appearance Settings',
      defaultTheme: 'cool',
      livePreview: true,
    }),
  ],
})
2) Inject theme variables in Next.js layout
import { ServerThemeInjector } from '@kilivi-dev/payloadcms-theme-management/server'
import configPromise from '@payload-config'
import { getPayload } from 'payload'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const payload = await getPayload({ config: configPromise })

  const appearance = await payload.findGlobal({
    slug: 'appearance-settings',
  })

  return (
    <html lang="en">
      <head>
        <ServerThemeInjector themeConfiguration={appearance?.themeConfiguration} />
      </head>
      <body>{children}</body>
    </html>
  )
}

Live Preview (v2.0.0)

Plugin now supports Payload-style preview targeting your client pages by slug.

  • home slug resolves to /
  • any other slug resolves to /{slug}
  • optional tenant query is appended when available
Basic setup
themeManagementPlugin({
  livePreview: true,
})
Advanced setup (with injected endpoint)
themeManagementPlugin({
  livePreview: {
    enabled: true,
    injectRoute: true,
    routePath: '/theme/preview',
    pageCollection: 'pages',
    pageSlug: 'home',
    fallbackToFirstPage: true,
    tenantField: 'tenant',
    tenantQueryParam: 'tenant',
    breakpoints: [
      { name: 'tablet', label: 'Tablet', width: 1024, height: 768 },
      { name: 'desktop', label: 'Desktop', width: 1440, height: 900 },
    ],
  },
})
Injected preview endpoint

If livePreview.injectRoute is enabled, plugin injects:

  • GET /api/theme/preview (or custom routePath)

Query params:

  • pageSlug
  • previewSecret (or preview)
  • tenant

Secret resolution:

  • PREVIEW_SECRET
  • PAYLOAD_PREVIEW_SECRET

Example:

/api/theme/preview?pageSlug=home&previewSecret=your-secret

Response:

  • 307 redirect to resolved client page path
  • 401 when secret is configured but invalid/missing
themeManagementPlugin({
  useStandaloneCollection: true,
  standaloneCollectionSlug: 'appearance-settings',
  livePreview: {
    enabled: true,
    injectRoute: true,
    routePath: '/theme/preview',
    pageCollection: 'pages',
    pageSlug: 'home',
  },
})

Preview request example:

/api/theme/preview?pageSlug=home&previewSecret=your-secret

Result:

  • redirects to /
Multi-tenant example
themeManagementPlugin({
  useStandaloneCollection: true,
  standaloneCollectionSlug: 'appearance-settings',
  livePreview: {
    enabled: true,
    injectRoute: true,
    routePath: '/theme/preview',
    pageCollection: 'pages',
    pageSlug: 'home',
    tenantField: 'tenant',
    tenantQueryParam: 'tenant',
  },
  cacheRevalidation: {
    enabled: true,
    injectRoute: true,
    routePath: '/theme/revalidate',
    secret: process.env.THEME_REVALIDATE_SECRET,
    tags: ['tenant:acme'],
    paths: ['/'],
  },
})

Preview request example:

/api/theme/preview?pageSlug=home&tenant=acme&previewSecret=your-secret

Result:

  • redirects to /?tenant=acme

Cache Revalidation

When useStandaloneCollection: true, default cache tag is:

  • global_{standaloneCollectionSlug}

Plugin can inject endpoint:

  • POST /api/theme/revalidate
themeManagementPlugin({
  useStandaloneCollection: true,
  cacheRevalidation: {
    enabled: true,
    injectRoute: true,
    routePath: '/theme/revalidate',
    secret: process.env.THEME_REVALIDATE_SECRET,
    tags: ['tenant:default'],
    paths: ['/'],
  },
})

Next.js server caching helper

import {
  createCachedThemeFetcher,
  ServerThemeInjector,
} from '@kilivi-dev/payloadcms-theme-management/server'
import configPromise from '@payload-config'
import { getPayload } from 'payload'

const getCachedTheme = createCachedThemeFetcher({
  globalSlug: 'appearance-settings',
  revalidate: 3600,
  loadAppearanceSettings: async () => {
    const payload = await getPayload({ config: configPromise })
    return payload.findGlobal({ slug: 'appearance-settings' })
  },
})

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const themeConfiguration = await getCachedTheme()

  return (
    <html lang="en">
      <head>
        <ServerThemeInjector themeConfiguration={themeConfiguration} />
      </head>
      <body>{children}</body>
    </html>
  )
}
Data fetch examples (fetchThemeConfiguration)

Single-tenant:

import { fetchThemeConfiguration } from '@kilivi-dev/payloadcms-theme-management'

const themeConfiguration = await fetchThemeConfiguration({
  useGlobal: true,
  collectionSlug: 'appearance-settings',
})

Multi-tenant:

import { fetchThemeConfiguration } from '@kilivi-dev/payloadcms-theme-management'

const tenantThemeConfiguration = await fetchThemeConfiguration({
  useGlobal: true,
  collectionSlug: 'appearance-settings',
  tenantSlug: 'acme',
})

Plugin options

Option Type Default Description
enabled boolean true Enables/disables plugin
targetCollection string 'site-settings' Collection slug for tab mode
useStandaloneCollection boolean false Creates standalone global instead of tab injection
standaloneCollectionSlug string 'appearance-settings' Global slug for standalone mode
standaloneCollectionLabel string | Record<string, string> translated label Label for standalone global
themePresets ThemePreset[] built-in presets Custom preset list
defaultTheme string 'cool' Default preset name
includeColorModeToggle boolean true Exposes light/dark/auto toggle
includeCustomCSS boolean true Enables custom CSS field
includeBrandIdentity boolean false Reserved for brand identity support
enableAdvancedFeatures boolean true Enables advanced theme controls
enableLogging boolean false Logs plugin actions
livePreview boolean | ThemeManagementLivePreviewOptions true Live preview URL behavior
cacheRevalidation boolean | ThemeManagementCacheRevalidationOptions auto (standalone default on) Cache invalidation endpoint/tags/paths
i18n ThemeManagementI18nOptions undefined Extend/override admin translations (see below)
livePreview options
Option Type Default
enabled boolean true
injectRoute boolean false
routePath string '/theme/preview'
pageCollection string 'pages'
pageSlug string 'home'
fallbackToFirstPage boolean true
tenantField string 'tenant'
tenantQueryParam string 'tenant'
breakpoints Array<{ name; label; width; height }> undefined
url (args) => string | Promise<string> default slug-based URL
cacheRevalidation options
Option Type Default
enabled boolean true in standalone mode, else false
injectRoute boolean true
routePath string '/theme/revalidate'
secret string undefined
tags string[] [global_{slug}]
paths string[] []

Internationalization (i18n)

The plugin ships with English (en) and Czech (cs) translations and registers them into Payload's native config.i18n under the theme-management namespace. Field labels are localized out of the box and the dynamic admin UI follows the active admin language.

Add languages or override individual strings via the i18n option (deep-merged over the built-ins; missing keys fall back to English):

import { de } from '@payloadcms/translations/languages/de'

themeManagementPlugin({
  i18n: {
    translations: {
      de: { tabLabel: 'Darstellung', ui: { lightMode: 'Heller Modus' } },
      en: { tabLabel: 'Theme' }, // override a built-in string
    },
    supportedLanguages: { de }, // optional: register a brand-new admin language
  },
})

Full guide: docs/TRANSLATIONS.md

Appearance Controls (v1.2.0)

The Appearance Settings tab contains three additional collapsible sections that give editors detailed control over the site's visual style.

Section Admin group Fields
Visual Effects themeConfiguration.visualEffects effectStyle, shadowIntensity, backdropBlur, borderStyle, borderWidth, glassOpacity
Hero & Background themeConfiguration.heroBackground heroStyle, heroHeight, gradientDirection, overlayOpacity, backgroundPattern, patternOpacity, sectionDivider, enableParallax
Component Styles themeConfiguration.componentStyles buttonVariant, buttonSize, cardStyle, cardHoverEffect, imageStyle, iconSet, navbarStyle, footerStyle, enableScrollReveal, enableHoverAnimations

Full reference: docs/APPEARANCE_CONTROLS.md

Reading appearance values in Next.js
import { fetchThemeConfiguration } from '@kilivi-dev/payloadcms-theme-management'

const theme = await fetchThemeConfiguration({ collectionSlug: 'site-settings' })

const effect = theme?.visualEffects?.effectStyle ?? 'flat'
const pattern = theme?.heroBackground?.backgroundPattern ?? 'none'
const button = theme?.componentStyles?.buttonVariant ?? 'filled'

return (
  <html data-effect={effect} data-pattern={pattern} data-button={button}>
    ...
  </html>
)

Theme Presets (70+)

Built-in presets are available from allThemePresets. New in v1.2.0:

import { allThemePresets } from '@kilivi-dev/payloadcms-theme-management'

// New visual-style presets (v1.2.0):
// 'glassmorphism' | 'claymorphism' | 'neumorphism' | 'aurora'
// 'luxury' | 'healthcare' | 'nordic' | 'warm-earth'

Full preset reference: docs/THEME_PRESETS_EXTENDED.md

Public API

Main package
import {
  fetchThemeConfiguration,
  generateThemeColorsCss,
  generateThemeCSS,
  getAvailableThemePresets,
  getThemePreset,
  getThemeStyles,
  resolveThemeConfiguration,
  themeManagementPlugin,
  ThemeProvider,
} from '@kilivi-dev/payloadcms-theme-management'
// TypeScript types (v1.2.0)
import type {
  SiteThemeConfiguration,
  ThemeComponentStyles,
  ThemeHeroBackground,
  ThemeVisualEffects,
} from '@kilivi-dev/payloadcms-theme-management'
Server package
import {
  createCachedThemeFetcher,
  getThemeCacheTag,
  getThemeCriticalCSS,
  getThemeCSS,
  revalidateThemeCache,
  ServerThemeInjector,
} from '@kilivi-dev/payloadcms-theme-management/server'

Notes

  • For server components, always import server-only helpers from @kilivi-dev/payloadcms-theme-management/server.
  • If your team uses strict preview security, set PREVIEW_SECRET and enable livePreview.injectRoute.
  • For multi-tenant apps, use tenantField + tenantQueryParam to keep preview URLs tenant-aware.

License

MIT

Keywords