Theme Options

Theme Options is a React admin page that lets editors control site-wide colors, typography, logos, social links, and third-party integration keys. Values are saved as a single JSON blob in wp_options and output as CSS custom properties via a static file at uploads/sanilwb/theme-vars.css, which is enqueued on the frontend.


File Map

Purpose File
React root — nav, save, reset orchestration admin/assets/js/src/theme-options/ThemeOptions.jsx
Colors section admin/assets/js/src/theme-options/sections/ColorsSection.jsx
Typography section admin/assets/js/src/theme-options/sections/TypographySection.jsx
Logo & Identity section admin/assets/js/src/theme-options/sections/LogoSection.jsx
Social Media section admin/assets/js/src/theme-options/sections/SocialSection.jsx
Integrations section admin/assets/js/src/theme-options/sections/IntegrationsSection.jsx
Variables section admin/assets/js/src/theme-options/sections/VariablesSection.jsx
Defaults — single source of truth for JS and PHP admin/assets/js/src/theme-options/data/defaults.json
Admin page HTML mount point admin/templates/theme-options/page.php
PHP: asset enqueue + window.SanilwbThemeOptions injection admin/class-sanil-website-builder-admin.phpenqueue_theme_options_assets() (line ~1446)
PHP: defaults loader (reads defaults.json, derives fonts + social) admin/class-sanil-website-builder-admin.phpget_theme_options_defaults() (line ~863)
PHP: saved options merged over defaults, migration, logo seed admin/class-sanil-website-builder-admin.phpget_theme_options() (line ~898)
PHP: AJAX save handler admin/class-sanil-website-builder-admin.phpajax_theme_options_save() (line ~997)
PHP: section reset handler (form POST, page reload) admin/class-sanil-website-builder-admin.phphandle_theme_options_reset() (line ~1066)
PHP: builds CSS string from saved options admin/class-sanilwb-admin-theme-options.phpbuild_theme_options_css()
PHP: writes CSS to uploads/sanilwb/theme-vars.css on save/reset admin/class-sanilwb-admin-theme-options.phpgenerate_theme_options_css_file()
PHP: enqueues the static CSS file on the frontend admin/class-sanilwb-admin-theme-options.phpenqueue_theme_options_css()
Admin CSS entry point admin/assets/css/theme-options.scss → compiled to theme-options.css

Data Flow

defaults.json
    │
    ├─► PHP: get_theme_options_defaults()     reads the file, derives `fonts` and `social` keys
    │         ↓
    │   get_theme_options()                   merges saved wp_options over defaults
    │         ↓
    │   enqueue_theme_options_assets()        injects result as window.SanilwbThemeOptions.data
    │
    └─► JS: ThemeOptions.jsx                  seeds React state from injected data,
                                              falling back key-by-key to DEFAULT_DATA

User edits in the UI
    ↓
handleSave() — POST to admin-ajax.php (action: sanilwb_theme_options_save)
    ↓
ajax_theme_options_save() — sanitizes, saves to wp_options('sanilwb_theme_options'),
                             syncs site icon, triggers font download,
                             regenerates uploads/sanilwb/theme-vars.css
    ↓
enqueue_theme_options_css() on wp_enqueue_scripts — enqueues the static file with a
                             timestamp-based version for browser cache busting

React App Architecture

ThemeOptions.jsx        ← root: sidebar nav, save/reset orchestration, active section state
├── ColorsSection       ← color presets and custom colors
├── TypographySection   ← font role pickers; live Google Fonts preview injection on change
├── LogoSection         ← desktop logo, mobile logo, featured image, site icon
├── SocialSection       ← platform URL fields
├── IntegrationsSection ← API keys, site language
└── VariablesSection    ← named pixel/string variables used as tokens in node settings

ThemeOptions (root)

  • Reads window.SanilwbThemeOptions.data (injected by PHP), falling back key-by-key to DEFAULT_DATA from defaults.json. Each top-level key is seeded independently so that a reset (which deletes one key from wp_options) causes only that section to fall back to code defaults — the rest are unaffected.
  • activeSection state controls which section component is rendered. On reset redirects, the ?section= query param restores the active section so the user lands back in the right place.
  • saveStatus state ('idle' | 'saving' | 'saved' | 'error') drives the Save button label and color.
  • showResetSuccess state is set to true when ?reset=success appears in the URL (written by PHP after a successful reset). Auto-dismissed after 3 seconds.
  • Google Fonts live preview: a module-level debouncedInjectFonts singleton is called whenever data.fonts changes (detected via a memoized JSON key). It injects <link> tags into <head> so the Typography section shows real font previews without requiring a save first.

NAV_ITEMS is defined at the top of ThemeOptions.jsx and controls sidebar order, icon, and reset button visibility.

id Label Dashicon supportsReset
colors Colors dashicons-art Yes
typography Typography dashicons-editor-textcolor Yes
logo Logo & Identity dashicons-format-image No
social Social Media dashicons-share No
integrations Integrations dashicons-block-default No
variables Variables dashicons-editor-code No

When supportsReset is true, the "Reset to Defaults" button appears in the top bar for that section. Clicking it submits a hidden form that POSTs to admin-post.php?action=sanilwb_theme_options_reset&section={activeSection}. PHP deletes that section's key from sanilwb_theme_options in wp_options, then redirects back with ?reset=success&section={activeSection}.


Database Storage

All options are saved under one wp_options key:

option_name: sanilwb_theme_options

The value is a JSON object with these top-level keys:

Key What it holds
colors active_preset, presets[], custom[]
fonts Object keyed by role (body, h1h6, button), each with family, weight, style
logo desktop, mobile, featuredImage — each { id, url }
siteIcon { id, url }
social Object keyed by platform slug, value is a URL string
integrations sharethis_property_id, facebook_app_id, site_language
variables Array of { id, name, value, type } objects — see Variables section below

PHP Global Injected on the Admin Page

enqueue_theme_options_assets() outputs this before the React bundle loads:

window.SanilwbThemeOptions = {
    data:         object,   // merged saved options (output of get_theme_options())
    nonce:        string,   // sanilwb_theme_options_save
    ajaxUrl:      string,   // admin-ajax.php URL
    resetNonce:   string,   // sanilwb_theme_options_reset
    adminPostUrl: string,   // admin-post.php URL
};

CSS Variables Output on the Frontend

build_theme_options_css() generates the CSS content. generate_theme_options_css_file() writes it to uploads/sanilwb/theme-vars.css on every save and reset. enqueue_theme_options_css() enqueues that file on wp_enqueue_scripts (frontend only). The file version is a Unix timestamp saved to wp_options('sanilwb_theme_css_version') so the browser cache busts on every update.

The file contains:

Colors — from the active preset:

:root {
    --color-primary: #1464b4;
    --color-secondary: #5f6368;
    --color-accent: #fbbc04;
    --color-text: #202124;
    --color-background: #ffffff;
    --color-link: #1464b4;
    --color-link-hover: #0a50a6;
}

Custom colors — appended with --color-custom-{uid}.

Typography — one set of three variables per font role:

:root {
    --font-body-family: 'Mukta', sans-serif;
    --font-body-weight: 400;
    --font-body-style: normal;
    --font-h1-family: 'Mukta', sans-serif;
    /* …and so on for h2–h6 and button */
}

Block editor utility classes — automatically generated so Gutenberg color choices render correctly:

.has-sanilwb-primary-color { color: var(--color-primary) !important; }
.has-sanilwb-primary-background-color { background-color: var(--color-primary) !important; }

Variables — one custom property per user-defined variable:

:root {
    --sanilwb-var-1: 24px;   /* number type — value stored as integer, emitted with px unit */
    --sanilwb-var-2: bold;   /* string type — value emitted as-is */
}

Use these CSS variables anywhere in the theme or preset SCSS files. They are always available on the frontend.

Important: After creating or editing variables in Theme Options, you must click Save to regenerate theme-vars.css. The new --sanilwb-var-N properties will not appear until the file is regenerated.


Sections

Colors

File: sections/ColorsSection.jsx

Manages color presets. Each preset contains the canonical set of named color slots (primary, secondary, accent, text, background, link, link-hover). The user can activate a preset or add custom colors with arbitrary UIDs.

Supports Reset to Defaults — clears the colors key from wp_options, forcing the JS to fall back to DEFAULT_DATA.colors on the next load.

Typography

File: sections/TypographySection.jsx

Each font role (body, h1–h6, button) stores { family, weight, style }. On save, the fonts are synced to SANILWB_Font_Sync which queues a Google Fonts download to the uploads directory.

Supports Reset to Defaults — clears the fonts key from wp_options.

Logo & Identity

File: sections/LogoSection.jsx

Manages desktop logo, mobile logo, default featured image, and site icon. All images are stored as { id, url } pairs (attachment ID + URL).

On save, the site icon is synced to the WP core site_icon option so it appears in the WP admin header and browser tab. Logo and featured image data are stored only in sanilwb_theme_options — all PHP readers use get_option('sanilwb_theme_options')['logo'] directly.

Does not support Reset to Defaults.

Social Media

File: sections/SocialSection.jsx

Platform list and order is defined in defaults.json under socialPlatforms[]. Each platform has key, label, icon (dashicons class), and placeholder. The saved value is just a URL string per platform key.

To add or remove a platform, edit defaults.json only — both JS and PHP derive the list from it automatically.

Integrations

File: sections/IntegrationsSection.jsx

Stores third-party API keys and site-level settings: ShareThis property ID, Facebook App ID, and site language (nepali or english).

site_language controls the language used throughout the site — date formats, UI labels, and Nepali/English text switches. PHP reads it as:

$site_language = get_option('sanilwb_theme_options')['integrations']['site_language'] ?? 'nepali';
$use_english   = $site_language !== 'nepali';

Does not support Reset to Defaults.

Variables

File: sections/VariablesSection.jsx

Variables are named, reusable values that can be inserted as tokens into pixel and text fields across Template Builder and Page Builder node settings. They let editors define a value once and reference it everywhere — changing the variable updates all nodes that use it on the next save.

Each variable has four fields:

Field Description
id Auto-incremented integer. Never changes after creation. Used as the token key.
name Human-readable label shown in the variable picker popover.
value The stored value — an integer for number type, any string for string type.
type 'number' or 'string'. Controls which fields the variable appears in.

Token format: {{sanilwb_var:N}} where N is the variable's id. This string is stored directly in a node field value when the user links a variable.

Type rules: - number variables appear in pixel input fields (font size, border radius, padding, margin, width, height, gap, etc.). The value is emitted as --sanilwb-var-N: Xpx in theme-vars.css. - string variables appear in text/textarea fields. The value is emitted as --sanilwb-var-N: value (no unit).

Token resolution at render time:

When a node field value is {{sanilwb_var:1}}, the compiler and JS preview both replace it with var(--sanilwb-var-1). The browser then resolves the CSS custom property to the declared value in theme-vars.css. This works in all three rendering contexts — public frontend, Template Builder canvas, and Page Builder canvas — because all three load theme-vars.css via wp_enqueue_scripts.

Adding a variable: 1. Go to Theme Options → Variables. 2. Click "Add Variable", set a name, choose type Number, and enter the pixel value (integer only, no px). 3. Click Save — this regenerates theme-vars.css with the new --sanilwb-var-N property. 4. Open any node in Template Builder or Page Builder, click the variable icon on a number field, and select the variable.

Does not support Reset to Defaults.


How defaults.json Works

defaults.json is the single source of truth for all default values. Both JS and PHP read it directly — there is no duplication.

JS side (ThemeOptions.jsx):

  • Imports defaults.json at build time.
  • Seeds React state: if a key is missing from window.SanilwbThemeOptions.data, it falls back to the corresponding value in DEFAULT_DATA.
  • fonts defaults are derived at runtime: fontRoles[].default is used to build the initial fonts object.
  • social defaults are derived at runtime: socialPlatforms[].key is mapped to empty strings.

PHP side (get_theme_options_defaults()):

  • Reads defaults.json with file_get_contents() and caches it statically for the request lifetime.
  • Derives fonts with array_column( $defaults['fontRoles'], 'default', 'key' ).
  • Derives social with array_fill_keys( array_column( $defaults['socialPlatforms'], 'key' ), '' ).

Rule: To change any default value, edit defaults.json only. Do not hardcode defaults in PHP or JS.


Reset Behavior

Only Colors and Typography support the "Reset to Defaults" button in the top bar.

Reset does not write factory values into the database. Instead, it deletes the section key from wp_options. On the next page load, the missing key causes JS to fall back to DEFAULT_DATA, which always reflects the current code defaults rather than a stale DB copy.

The reset is a plain HTML form POST (not AJAX) — the browser reloads after handle_theme_options_reset() processes it. After reload, the URL carries ?reset=success which triggers a temporary success banner in React.


Font Download on Save

When options are saved, ajax_theme_options_save() calls:

SANILWB_Font_Sync::update_entity_fonts( 'theme_options', $theme_fonts );

This queues the selected Google Fonts for download to the uploads directory. The download itself is handled by includes/class-sanilwb-font-downloader.php and includes/class-sanilwb-font-sync.php.


How to Add a New Color to the Default Palette

  1. Open admin/assets/js/src/theme-options/data/defaults.json.
  2. Add a new entry to colors.presets[0].colors[] with key, label, and value.
  3. That's it. get_theme_options() will automatically:
  4. Add the new color to any existing preset that is missing it (per-preset key-merge logic).
  5. Output a new --color-{key} CSS variable on the frontend.
  6. Generate .has-sanilwb-{key}-color and .has-sanilwb-{key}-background-color utility classes.

How to Add a New Font Role

  1. Open defaults.json and add a new entry to fontRoles[]:

json { "key": "caption", "label": "Captions", "hint": "Small image captions", "default": { "family": "Mukta", "weight": 400, "style": "normal" } }

  1. Both JS and PHP derive the fonts object from fontRoles, so the new role appears automatically with no other changes.
  2. build_theme_options_css() will output --font-caption-family, --font-caption-weight, and --font-caption-style variables automatically on the next save.

How to Add a New Social Platform

  1. Open defaults.json and add an entry to socialPlatforms[]:

json { "key": "threads", "label": "Threads", "icon": "dashicons-admin-users", "placeholder": "https://threads.net/@yourhandle" }

  1. The platform appears in the Social Media section automatically. No JS or PHP changes needed.

How to Add a New Integration Field

  1. Add the field key and default value to defaults.json under integrations:

json "integrations": { "sharethis_property_id": "", "facebook_app_id": "", "site_language": "nepali", "google_analytics_id": "" }

  1. Add the corresponding input to sections/IntegrationsSection.jsx — copy the pattern of an existing field row.
  2. If PHP needs to read the value (e.g. to output a script tag), read it as:

php get_option('sanilwb_theme_options')['integrations']['google_analytics_id'] ?? ''


How to Add a New Section

  1. Create sections/YourSection.jsx. It receives data (the section's slice of state) and onChange(newValue) as props.
  2. Add the section to NAV_ITEMS in ThemeOptions.jsx:

js { id: 'your-section', label: 'Your Section', icon: 'dashicons-admin-generic', supportsReset: false }

  1. Add a top-level key and its defaults to defaults.json.
  2. Add the state seed in the useState initializer in ThemeOptions.jsx (same pattern as social or integrations).
  3. Render the section component in the to-content block, guarded by activeSection === 'your-section'.
  4. Add a case in ajax_theme_options_save() if the section needs special sanitization or sync behavior.
  5. If the section's values should output CSS variables, extend build_theme_options_css() in class-sanilwb-admin-theme-options.php.