Skip to main content

theme.config.ts

theme.config.ts is the “shape” of your theme:
  • which settings exist (colors, typography, etc.)
  • which sections exist (header, hero, footer, etc.)
  • which fields are editable in the Theme Builder
Keep it pure data: no runtime logic, no network calls.
Think of theme.config.ts as a schema for customization. Your theme reads the values at runtime and decides how to render them.

Minimal Example

// @validate
import type { ThemeConfig } from '@shoppex/sdk';

export const themeConfig = {
  id: 'my-theme',
  name: 'My Theme',
  version: '1.0.0',
  settings: {
    colors: {
      primary: { type: 'color', label: 'Primary', default: '#7c3aed' },
    },
  },
  sections: {
    header: {
      name: 'Header',
      description: 'Top navigation',
      settings: {
        sticky: { type: 'boolean', label: 'Sticky header', default: true },
      },
    },
  },
} satisfies ThemeConfig;

How Shoppex Uses This (Conceptually)

In short:
  1. Shoppex reads your config to know what is customizable.
  2. Merchants change values in a Theme Builder UI.
  3. Shoppex publishes the settings for a shop.
  4. Your theme fetches the published settings and applies them (usually as CSS variables).

Setting Field Types

Every field in settings or sections[*].settings must have a type. There are 10 types:

color

A hex color picker.
primary: { type: 'color', label: 'Primary', default: '#7c3aed' }

text

A single-line text input.
title: { type: 'text', label: 'Store Title', default: 'Welcome' }

range

A numeric slider with bounds.
fontSize: {
  type: 'range',
  label: 'Base Font Size',
  default: 14,
  min: 12,
  max: 20,
  step: 1,   // optional, defaults to 1
  unit: 'px' // optional, displayed in the UI
}

select

A dropdown. Options can be plain strings or { value, label } objects.
// Simple options
layout: {
  type: 'select',
  label: 'Layout',
  default: 'grid',
  options: ['grid', 'list']
}

// Labeled options
layout: {
  type: 'select',
  label: 'Layout',
  default: 'grid',
  options: [
    { value: 'grid', label: 'Grid Layout' },
    { value: 'list', label: 'List Layout' },
  ]
}

boolean

A toggle switch.
sticky: { type: 'boolean', label: 'Sticky Header', default: true }

image

A single image upload. Default is usually null.
logo: { type: 'image', label: 'Logo', default: null }

font

A font family picker. Optionally restrict choices with options.
fontFamily: {
  type: 'font',
  label: 'Body Font',
  default: 'Inter',
  options: ['Inter', 'Roboto', 'Open Sans'] // optional
}

richtext

A rich-text editor (HTML output).
about: { type: 'richtext', label: 'About Text', default: '' }

products

A product picker. Returns an array of product IDs.
featured: {
  type: 'products',
  label: 'Featured Products',
  default: [],
  max: 6 // optional, limits the selection
}

images

A multi-image upload. Returns an array of URLs.
gallery: {
  type: 'images',
  label: 'Gallery',
  default: [],
  max: 10 // optional
}

TypeScript Interfaces

The full type definitions live in packages/sdk/src/types/theme-config.ts.
import type {
  ThemeConfig,
  SectionDefinition,
  SettingField,
} from '@shoppex/sdk';

// ThemeConfig — top-level config shape
// {
//   id: string;
//   name: string;
//   description?: string;
//   version: string;
//   author?: string;
//   preview?: string;
//   settings: Record<string, Record<string, SettingField>>;
//   sections: Record<string, SectionDefinition>;
// }

// SectionDefinition — a section with its own settings
// {
//   name: string;
//   description?: string;
//   icon?: string;
//   settings: Record<string, SettingField>;
// }

// SettingField — union of all 10 field types:
// ColorField | TextField | RangeField | SelectField | BooleanField
// | ImageField | FontField | RichtextField | ProductsField | ImagesField
Use satisfies ThemeConfig (not as ThemeConfig) so TypeScript validates the structure while preserving exact types for autocomplete.

Reference Config (Default Theme)

A condensed version of the reference theme’s config showing all settings groups and sections (values taken from themes/default/theme.config.ts):
import type { ThemeConfig } from '@shoppex/sdk';

export const themeConfig = {
  id: 'default',
  name: 'Default',
  description: 'Default Shoppex theme',
  version: '1.0.4',
  author: 'Shoppex',
  preview: '/theme.preview.png',

  settings: {
    // 14 color fields
    colors: {
      background:   { type: 'color', label: 'Background',    default: '#09090b' },
      surface:      { type: 'color', label: 'Surface',       default: '#18181b' },
      primary:      { type: 'color', label: 'Primary',       default: '#7c3aed' },
      primaryDark:  { type: 'color', label: 'Primary Dark',  default: '#8b5cf6' },
      secondary:    { type: 'color', label: 'Secondary',     default: '#8b5cf6' },
      text:         { type: 'color', label: 'Text',          default: '#f4f4f5' },
      textMuted:    { type: 'color', label: 'Muted Text',    default: '#a1a1aa' },
      textContrast: { type: 'color', label: 'Text Contrast', default: '#71717a' },
      border:       { type: 'color', label: 'Border',        default: '#27272a' },
      muted:        { type: 'color', label: 'Muted Surface', default: '#202023' },
      accent:       { type: 'color', label: 'Accent',        default: '#27272a' },
      hover:        { type: 'color', label: 'Hover',         default: '#27272a' },
      success:      { type: 'color', label: 'Success',       default: '#22c55e' },
      error:        { type: 'color', label: 'Error',         default: '#ef4444' },
    },

    // Typography
    typography: {
      fontFamily:  { type: 'font',  label: 'Body Font',    default: 'Inter' },
      headingFont: { type: 'font',  label: 'Heading Font', default: 'Inter' },
      fontSize:    { type: 'range', label: 'Font Size',    default: 14, min: 12, max: 20 },
    },

    // Effects
    effects: {
      borderRadius: { type: 'range', label: 'Border Radius', default: 7.2, min: 0, max: 24, step: 0.5, unit: 'px' },
    },

    // Buttons
    buttons: {
      borderRadius: { type: 'range', label: 'Button Radius', default: 8, min: 0, max: 24, step: 1, unit: 'px' },
    },

    // Header
    header: {
      height: { type: 'range', label: 'Header Height', default: 64, min: 48, max: 96, step: 2, unit: 'px' },
    },

    // Inputs
    inputs: {
      height:       { type: 'range', label: 'Input Height', default: 40, min: 32, max: 56, step: 2, unit: 'px' },
      borderRadius: { type: 'range', label: 'Input Radius', default: 8,  min: 0,  max: 20, step: 1, unit: 'px' },
    },
  },

  sections: {
    header: {
      name: 'Header',
      settings: {
        sticky:   { type: 'boolean', label: 'Sticky Header', default: true },
        showCart:  { type: 'boolean', label: 'Show Cart Icon', default: true },
      },
    },
    footer: {
      name: 'Footer',
      settings: {
        showNewsletter: { type: 'boolean', label: 'Show Newsletter', default: true },
      },
    },
    hero: {
      name: 'Hero',
      settings: {
        title:    { type: 'text', label: 'Title',    default: 'Welcome' },
        subtitle: { type: 'text', label: 'Subtitle', default: '' },
      },
    },
    productGrid: {
      name: 'Product Grid',
      settings: {
        showSearch: { type: 'boolean', label: 'Show Search', default: true },
      },
    },
  },
} satisfies ThemeConfig;

Common Mistakes

Keep runtime code in src/. The config should be plain data so tools can parse it safely.
Treat keys like colors.primary as stable. Changing keys breaks existing published settings.

Next Steps

Sources

  • Theme config types: packages/sdk/src/types/theme-config.ts
  • Reference theme config: themes/default/theme.config.ts