Tutorial: Configuring Theme Options
Theme Options is the admin panel for site-wide design settings — colors, typography, logos, social links, and third-party integration keys. Values are saved as a JSON blob in wp_options and applied to the frontend as CSS custom properties via a static file at uploads/sanilwb/theme-vars.css, which is enqueued on every public-facing page.
This tutorial covers how to use and configure every section, how the CSS variables flow to the frontend, and how to extend the system (add new colors, font roles, or social platforms) by editing defaults.json.
Opening Theme Options
Go to WordPress Admin → Theme Options in the Sanil Website Builder menu group. The page opens on the last active section (Colors by default).
The left sidebar is the section navigator. Click any section name to switch to it.
Understanding the Data Flow
Before changing settings, it helps to understand how data moves through the system:
defaults.json (code)
↓
PHP: get_theme_options_defaults() reads the file, caches for the request
↓
PHP: get_theme_options() merges saved wp_options over defaults
↓
JS: window.SanilwbThemeOptions.data injected on the admin page
↓
React: ThemeOptions.jsx seeds state, falls back key-by-key to defaults.json
User saves in the UI
↓
AJAX POST → ajax_theme_options_save() sanitizes + writes to wp_options('sanilwb_theme_options')
triggers font download
regenerates uploads/sanilwb/theme-vars.css
↓
Frontend: theme-vars.css enqueued on wp_enqueue_scripts
The uploads/sanilwb/theme-vars.css file is regenerated on every save and reset. It is versioned with a Unix timestamp so the browser cache busts automatically.
Colors
Section: Colors
The Colors section manages a palette of named color slots. Each color slot has a consistent name used throughout the plugin and theme.
Default color slots
| Slot key | CSS variable | Purpose |
|---|---|---|
primary |
--color-primary |
Main brand color — buttons, links, accents |
secondary |
--color-secondary |
Supporting brand color |
accent |
--color-accent |
Highlight color — tags, call-to-actions |
text |
--color-text |
Default body text color |
background |
--color-background |
Default page background color |
link |
--color-link |
Link text color |
link-hover |
--color-link-hover |
Link hover color |
Color presets
The Colors section shows one or more presets — named groups of color values that set all slots at once. Clicking a preset activates it and updates all color slots simultaneously.
To switch to a preset:
- Click the preset name in the Colors section.
- The color swatches update to show the preset's values.
- Click Save to apply the preset's colors to the live site.
Presets are defined in defaults.json under colors.presets[]. Each preset has a name and a colors{} object mapping slot keys to hex values.
Custom colors
Below the presets, a Custom Colors area lets you add arbitrary named colors. These appear in the generated CSS as --color-custom-{uid} variables and in the block editor as custom color options.
To add a custom color:
- Click Add Custom Color.
- Give it a label (e.g. "Promo Banner Red").
- Set the color value using the color picker.
- Click Save.
The label is for display only — the CSS variable name is generated from a uid, not the label.
Reset Colors to Defaults
Click Reset to Defaults in the top bar to clear the saved colors key from wp_options. On the next page load, the JS falls back to DEFAULT_DATA.colors (from defaults.json). This is not reversible through the UI — any custom color values you entered will be lost.
Typography
Section: Typography
The Typography section assigns a font to each font role — named slots like body, h1, h2, button, etc. Each role stores three values:
| Property | What it controls |
|---|---|
| Family | The Google Font family name |
| Weight | Font weight (e.g. 400, 700) |
| Style | Normal or Italic |
Changing a font role
- Click the Font Family dropdown for the role you want to change.
- Search or scroll to find a Google Font.
- The Typography section injects a Google Fonts
<link>tag into the page<head>immediately (without saving) so you can see a live preview of the font in the picker. - Select a Font Weight from the updated dropdown. The weight options filter to what the selected font actually supports.
- Click Save when you are happy with all roles.
What happens on save
When font settings are saved:
ajax_theme_options_save()callsSANILWB_Font_Sync::update_entity_fonts('theme_options', $fonts).- This queues the selected Google Fonts for download to the uploads directory (
uploads/sanilwb/fonts/). generate_theme_options_css_file()writes new font CSS variables totheme-vars.css.
Generated CSS variables
One set of three variables is generated per font role:
:root {
--font-body-family: 'Mukta', sans-serif;
--font-body-weight: 400;
--font-body-style: normal;
--font-h1-family: 'Lato', sans-serif;
--font-h1-weight: 700;
--font-h1-style: normal;
/* …h2–h6, button */
}
The theme applies these variables to the matching HTML elements. For example, the theme stylesheet uses font-family: var(--font-body-family) on the body element.
Reset Typography to Defaults
Click Reset to Defaults to clear the saved fonts key from wp_options. The JS falls back to the fontRoles[].default values from defaults.json.
Logo & Identity
Section: Logo & Identity
This section manages image assets for the site brand.
| Field | What it stores | Where it is used |
|---|---|---|
| Desktop Logo | Attachment { id, url } |
$common['logo_html'] in all preset templates |
| Mobile Logo | Attachment { id, url } |
Some presets show a different (smaller) logo on mobile |
| Default Featured Image | Attachment { id, url } |
Fallback image for posts without a featured image |
| Site Icon (Favicon) | Attachment { id, url } |
Browser tab icon, WP admin header |
Uploading a logo
- Click Select Image next to the field.
- The WordPress Media Library opens. Upload a new file or select an existing one.
- Click Select. The image URL populates the field and a preview appears.
- Click Save.
On save, the Site Icon is also synced to WordPress core's site_icon option so it appears in the admin header and browser tabs immediately.
Important: Logo and featured image data are stored only in sanilwb_theme_options. All PHP code that reads the logo uses get_option('sanilwb_theme_options')['logo'] directly — it does not rely on WordPress's built-in custom logo feature.
Why there is no Reset button for Logo & Identity
Resetting logos would delete the saved attachment IDs, leaving the site without a logo. There is no safe "default" logo to fall back to, so this section intentionally omits the reset button.
Social Media
Section: Social Media
This section stores a URL for each social platform. The URLs are output in the footer and other preset templates that include social links.
Each platform has a text input for its URL. Leave a platform blank if you don't use it — blank values are not output in templates.
The platform list (order, labels, icons) is defined entirely in defaults.json under socialPlatforms[]. To add or remove a platform, edit defaults.json — no JS or PHP changes are needed.
Example: adding a Threads platform
Open admin/assets/js/src/theme-options/data/defaults.json and add to socialPlatforms[]:
{
"key": "threads",
"label": "Threads",
"icon": "dashicons-admin-users",
"placeholder": "https://threads.net/@yourhandle"
}
The Threads field now appears automatically in the Social Media section without any other changes. On save, the value is stored as social.threads in the options JSON.
Integrations
Section: Integrations
This section stores third-party API keys and site-level configuration.
| Field | Key | What it does |
|---|---|---|
| ShareThis Property ID | sharethis_property_id |
Property ID for the ShareThis share widget |
| Facebook App ID | facebook_app_id |
App ID for Facebook Open Graph / sharing |
| Site Language | site_language |
'nepali' or 'english' — controls date formats and UI text throughout the site |
Site language
The site_language setting controls which date format and UI labels are used across the frontend. PHP reads it as:
$site_language = get_option('sanilwb_theme_options')['integrations']['site_language'] ?? 'nepali';
$use_english = $site_language !== 'nepali';
Set it to english if you are building an English-language site. The default is nepali.
How to Use CSS Variables in Presets and Stylesheets
The CSS variables from Theme Options are available globally on the frontend. Use them in any preset's style.scss or the theme's own stylesheets:
.my-banner {
background-color: var(--color-primary);
color: var(--color-background);
font-family: var(--font-h2-family);
font-weight: var(--font-h2-weight);
}
You can also use them in manifest slot default values so preset settings automatically pull from the current theme colors:
{
"id": "nav_bg",
"type": "color",
"label": "Navigation Background",
"default": "var(--color-primary)",
"breakpoints": ["desktop"]
}
This means switching a Theme Options color preset instantly updates the default value for that slot on any preset that references it.
How to Extend Theme Options
All extensions start from defaults.json. Both JS and PHP derive their defaults from this single file — there is no duplication.
Adding a new color to the default palette
Open admin/assets/js/src/theme-options/data/defaults.json. Find colors.presets[0].colors[] and add:
{ "key": "highlight", "label": "Highlight", "value": "#ffd700" }
That's all. On the next save, get_theme_options() adds highlight to any existing preset that is missing it, and build_theme_options_css() outputs --color-highlight: #ffd700 automatically.
Adding a new font role
Add to fontRoles[] in defaults.json:
{
"key": "caption",
"label": "Captions",
"hint": "Small image captions",
"default": { "family": "Mukta", "weight": 400, "style": "normal" }
}
Both PHP and JS derive the fonts object from fontRoles, so the new role appears in the Typography section and generates --font-caption-family, --font-caption-weight, --font-caption-style variables on the next save.
Adding a new social platform
Add to socialPlatforms[] in defaults.json:
{
"key": "bluesky",
"label": "Bluesky",
"icon": "dashicons-share",
"placeholder": "https://bsky.app/profile/yourhandle"
}
The platform appears in the Social section immediately. No JS or PHP changes needed.
Adding a new integration field
- Add the key and default to
defaults.jsonunderintegrations:
json
"integrations": {
"sharethis_property_id": "",
"facebook_app_id": "",
"site_language": "nepali",
"google_analytics_id": ""
}
- Add an input field to
sections/IntegrationsSection.jsxfollowing the pattern of an existing field row. - Read the value in PHP where needed:
php
$ga_id = get_option('sanilwb_theme_options')['integrations']['google_analytics_id'] ?? '';
Adding a completely new section
- Create
admin/assets/js/src/theme-options/sections/YourSection.jsx. It receivesdata(that section's state slice) andonChange(newValue)as props. - Add the section to
NAV_ITEMSinThemeOptions.jsx:js { id: 'your-section', label: 'Your Section', icon: 'dashicons-admin-generic', supportsReset: false } - Add a top-level key to
defaults.jsonwith its default values. - Seed the state in
ThemeOptions.jsx'suseStateinitializer, following the same pattern associalorintegrations. - Render the section component inside the
to-contentblock guarded byactiveSection === 'your-section'. - Add a case in
ajax_theme_options_save()if the section needs special sanitization or sync behavior on save. - If the section outputs CSS variables, extend
build_theme_options_css()inadmin/class-sanilwb-admin-theme-options.php.
Common Mistakes
Colors changed in Theme Options but presets still show the old color
The CSS variables are written to uploads/sanilwb/theme-vars.css on save. If the file was not regenerated (save failed silently), the old file is still served. Check the browser DevTools Network tab to confirm theme-vars.css is loading and has the new values. Also confirm the file's ?ver= timestamp updated.
A font picked in Typography isn't showing on the frontend
The font file must be downloaded first. SANILWB_Font_Sync queues the download on save — if the download hasn't completed yet, the browser will fall back to the sans-serif generic family. Wait a moment and reload. If it still doesn't work, check uploads/sanilwb/fonts/ to see if the font files arrived.
Resetting Colors wiped my custom colors too
Reset clears the entire colors key from wp_options, which includes both the active preset and all custom colors. This is intentional — the reset restores the code defaults from defaults.json. There is no selective reset. Back up your custom color values before clicking Reset.
A defaults.json change isn't appearing in the admin
PHP caches the defaults file statically per request (get_theme_options_defaults() uses a static variable). If you are testing in the same browser tab where the page was loaded before the change, reload the admin page to get a fresh PHP request.
The site icon isn't showing in the browser tab
On save, the Site Icon is synced to WordPress core's site_icon option. If it's still not showing, check get_option('site_icon') in a plugin or with WP-CLI to confirm the ID was written. Also check whether a caching plugin is serving an old <head>.