TAW FrameworkTAW Theme

Theme boot and performance

Initialize the TAW Core theme bootstrap, configure performance optimizations, and discover helper classes you will use in your templates.

When and how to boot the theme

Call TAW\Core\Theme::boot() once from your theme to wire up block loading, Vite assets, visual editor behavior, REST endpoints, and SVG support.

Theme::boot() hooks into core WordPress actions:

  • On after_setup_theme, it:

    • Calls BlockLoader::loadAll() to register all TAW Core blocks.

    • Calls ViteLoader::init() to prepare your asset pipeline.

  • On wp_enqueue_scripts, it:

    • Calls BlockRegistry::enqueueQueuedAssets() to enqueue CSS/JS requested by blocks during render.

    • Registers SearchEndpoints for the taw/v1/search-posts REST endpoint.

    • Calls Svg::register() to enable safe SVG upload and sanitization.

Theme::boot() accepts an optional config array (e.g. ['performance' => [...]]) and is designed to run exactly once per request. Call it from your theme bootstrap (for example, functions.php) before you add custom hooks that depend on blocks or Vite.

The Visual Editor is opt-in. Call VisualEditor::enable() before Theme::boot() to activate it — in scaffolds using Theme::bootstrapFullSite(), that means inc/customizations.php, not functions.php (which is framework-owned). See the Visual Editor section for the full API and template annotation helpers.

The icon field type is opt-in — call Lucide::enable() in inc/customizations.php before Theme::boot(). It's commented out by default in this theme's scaffold. See Icon System.

Media Folders is opt-in at the taw/core level, but this theme's inc/customizations.php scaffold calls MediaFolders::enable() by default, so every new taw-theme site has nestable Media Library folders active out of the box. See Media Folders.

Set up your theme by booting TAW Core and then configuring performance. Use this flow in your functions.php (or equivalent) entry point.

Require the TAW Core autoloader

Make sure WordPress can load the TAW\Core\Theme class. In most setups you load this via Composer in your theme or a must-use plugin.

<?php
// functions.php

require __DIR__ . '/vendor/autoload.php';

Boot the theme systems

Call Theme::boot() early so blocks, Vite, editor integration, REST endpoints, and SVG support are ready when WordPress runs its hooks.

<?php

use TAW\Core\Theme;

Theme::boot();

Configure performance options

Configure theme-level performance behavior through Theme::performance(array $config). This passes your options through to the internal Performance::configure() layer.

<?php

use TAW\Core\Theme;

Theme::boot();

Theme::performance([
    'remove_bloat'        => true,
    'remove_emoji'        => true,
    'remove_meta_tags'    => true,
    'remove_oembed'       => true,
    'preconnect_origins'  => [
        'https://fonts.gstatic.com',
        'https://cdn.taw-assets.com',
    ],
    'preload_fonts'       => [
        'resources/fonts/inter-var.woff2',
    ],
    'preload_images'      => [
        // [attachment_id, size]
        [1234, 'hero-desktop'],
    ],
]);

Minimal functions.php example

For a clean theme that only uses the defaults and basic performance flags:

<?php
// functions.php

use TAW\Core\Theme;

Theme::boot();

Theme::performance([
    'remove_bloat'     => true,
    'remove_emoji'     => true,
    'remove_meta_tags' => true,
    'remove_oembed'    => true,
]);

You can pass a performance key directly to Theme::boot() as a shorthand instead of calling Theme::performance() separately:

Theme::boot([
    'performance' => [
        'remove_bloat'     => true,
        'remove_emoji'     => true,
        'remove_meta_tags' => true,
        'remove_oembed'    => true,
    ],
]);

Both approaches are equivalent. Use whichever fits your functions.php layout.

You should now see your custom blocks available in the editor, Vite-powered assets loading on the front end, and unnecessary WordPress bloat removed from the page output.

Performance configuration reference

Theme::performance(array $config) accepts a keyed array and forwards it to Performance::configure(). All keys are optional; omit a key to leave the existing behavior unchanged.

Use these configuration options to strip unneeded WordPress features and add preload and preconnect hints.

Start with only remove_bloat, remove_emoji, remove_meta_tags, and remove_oembed enabled. Add preconnect_origins, preload_fonts, and preload_images once you know which assets are critical for your theme.

Boolean flags

body
remove_bloatbool

Remove common WordPress front-end bloat such as unnecessary styles, scripts, and features that TAW Core does not rely on. Enable this to reduce page weight and HTTP requests.

body
remove_emojibool

Disable WordPress emoji detection scripts and related assets. Turn this on if you do not need WordPress emoji support and want to avoid extra requests.

body
remove_meta_tagsbool

Remove default WordPress meta tags from the document head, such as generator tags and other non-essential metadata.

body
remove_oembedbool

Turn off WordPress oEmbed support and associated discovery links and scripts. Use this when you do not rely on automatic oEmbed handling for content.

Resource hints and preloads

body
preconnect_originsstring[]

List of origin URLs to preconnect to, such as https://fonts.gstatic.com or your asset CDN. The performance layer emits preconnect hints so the browser establishes connections earlier.

body
preload_fontsstring[]

Array of font paths that TAW Core resolves using ViteLoader::assetUrl. Paths are relative to your Vite asset root, for example resources/fonts/inter-var.woff2. The performance layer preloads these fonts as font resources.

body
preload_imagesarray[]

Array of two-element arrays describing images to preload, each in the form [attachment_id, size]. The performance layer resolves the image source for the given attachment and size and emits <link rel="preload"> tags. Use this only for above-the-fold images that affect first paint; it supersedes older single-image approaches such as preload_hero_image.

Keep preload_images scoped to a small set of critical hero or navigation images. Over-preloading below-the-fold images can delay more important resources.

Template helpers you will use often

Once the theme is booted and performance is configured, use helper classes from the TAW\Helpers namespace to keep your templates concise and consistent.

Each helper has its own dedicated documentation. Use this section as an orientation and then follow the links when you are ready to integrate them into templates and metabox UIs.

Vite asset helpers (TAW\Support\ViteLoader)

ViteLoader is the OOP Vite bridge shipped in taw/core. It is PSR-4 autoloaded — no explicit include needed.

body
ViteLoader::assetUrl($path)string
Required

Resolve a theme asset path to its URL — returns the content-hashed production URL or the Vite dev server URL during development.

<?php

use TAW\Support\ViteLoader;

// Resolve a self-hosted font file
$fontUrl = ViteLoader::assetUrl('resources/fonts/Inter-Regular.woff2');

echo '<link rel="preload" href="' . esc_url($fontUrl) . '" as="font" crossorigin>';
body
ViteLoader::isDevServerRunning()bool

Return true when the Vite dev server is running. Use this for dev-only logic such as disabling caches or injecting debug markup.

<?php

use TAW\Support\ViteLoader;

if (ViteLoader::isDevServerRunning()) {
    // dev-only logic
}

The legacy procedural functions vite_asset_url() and vite_is_dev() are no longer in the Composer files autoload and are not available globally. Use ViteLoader::assetUrl() and ViteLoader::isDevServerRunning() instead.

TAW\Helpers\Image

TAW\Helpers\Image generates <img> tags with the correct loading, fetchpriority, decoding, srcset, and sizes attributes based on whether the image is above or below the fold.

  • Render optimized <img> tags with proper performance attributes.

  • Generate <link rel="preload"> tags for above-the-fold images.

  • Work with WordPress attachment IDs rather than hard-coded URLs.

Use this helper in hero sections, featured images, and any template that must load quickly on first paint.

Commonly used methods

body
Image::render($id, $size, $options = [])string
Required

Render an <img> tag with automatically set loading, fetchpriority, decoding, srcset, and sizes attributes. Pass ['above_fold' => true] for hero images that should load eagerly with high priority.

<?php

use TAW\Helpers\Image;

// Above-the-fold hero (eager, high priority)
echo Image::render($hero_id, 'full', ['above_fold' => true]);

// Regular image (lazy, low priority — the default)
echo Image::render(get_post_thumbnail_id(), 'large');

// With CSS class and custom sizes attribute
echo Image::render($id, 'large', [
    'class' => 'rounded-lg shadow-md',
    'sizes' => '(max-width: 768px) 100vw, 50vw',
]);

// With arbitrary extra attributes
echo Image::render($id, 'medium', [
    'attr' => ['id' => 'site-logo', 'data-hero' => 'true'],
]);
body
Image::preloadTag($id, $size)string
Required

Generate a <link rel="preload"> tag for your single most important image. Call before wp_head() or hook at priority 1–2 to ensure it lands early in <head>.

<?php

use TAW\Helpers\Image;

echo Image::preloadTag($hero_id, 'full');
// → <link rel="preload" href="..." as="image" imagesrcset="..." imagesizes="...">

TAW\Helpers\Svg

TAW\Helpers\Svg works together with Svg::register() from the theme boot process to:

  • Allow safe SVG uploads and sanitize content.

  • Render SVGs inline in your templates.

  • Generate URLs to SVG assets managed by TAW Core.

Use this helper when you want sharp icons and logos without relying on raster images.

body
Svg::register()void
Required

Register SVG upload support and sanitization hooks with WordPress. Theme::boot() calls this for you during the WordPress lifecycle so uploads and renders are safe by default.

<?php

use TAW\Helpers\Svg;

// You normally do not need to call this manually because Theme::boot() handles it.
// Svg::register();
body
Svg::render($id, $alt, $options = [])string
Required

Render a sanitized <img> tag for an SVG attachment. Use this when you want a standard image element with an accessible alt attribute and do not need to target individual SVG paths in CSS or JavaScript.

<?php

use TAW\Helpers\Svg;

echo Svg::render(5678, 'Company logo', [
    'class' => 'site-logo',
]);
body
Svg::inline($id, $options = [])string
Required

Output inline SVG markup so you can style or animate SVG elements directly with CSS or JavaScript. Use this for icons that need currentColor, hover effects, or timeline animations.

<?php

use TAW\Helpers\Svg;

echo '<button class="icon-button">';
echo Svg::inline(9012, [
    'class' => 'icon icon-play',
]);
echo '</button>';
body
Svg::url($id)string
Required

Return the URL for a stored SVG attachment. Use this when you need to reference the SVG in CSS, as a background image, or to hand off to client-side code.

<?php

use TAW\Helpers\Svg;

$iconUrl = Svg::url(9012);

echo '.icon-custom { background-image: url(' . esc_url($iconUrl) . '); }';

TAW sanitizes SVGs on upload via the hooks registered in Svg::register(). Use Svg::render() and Svg::inline() instead of echoing raw attachment content so your templates benefit from the sanitization pipeline.

Choosing between render and inline

Use Svg::render() when you want a simple <img> tag with an alt attribute and do not need to manipulate the SVG internals. Use Svg::inline() when you need CSS access to paths and groups or want to drive animations.

<?php

use TAW\Helpers\Svg;

// Standard logo as an <img> element.
echo Svg::render(5678, 'Header logo', [
    'class' => 'site-logo',
]);

// Inline SVG icon that inherits text color and can be animated.
echo Svg::inline(9012, [
    'class' => 'icon icon-arrow',
]);

Menus are registered in functions.php via register_nav_menus(). Add or rename locations there directly, then assign menus to locations in WordPress Admin → Appearance → Menus.

// functions.php
register_nav_menus([
    'primary' => __('Primary Navigation', 'taw'),
    'footer'  => __('Footer Navigation', 'taw'),
]);

To render menus in templates, use TAW\Core\Menu\Menu::get($location). See the Navigation menus reference in the TAW Core tools overview for the full Menu and MenuItem API.

Boilerplate blocks

TAW Theme ships with ready-to-use blocks you can drop in immediately or treat as reference implementations when building your own.

Hero

Blocks/Hero/ is a full-featured MetaBlock that demonstrates the breadth of TAW Core's metabox field system. It serves both as a usable hero section and as a reference for advanced field patterns.

Registered fields:

  • heading and tagline — text inputs (with a checkbox to toggle tagline visibility)
  • content — wysiwyg rich text editor
  • image — background image picker
  • bg_color — color picker for section background
  • padding — range slider (20–200 px, default 80 px)
  • featured_post / related_posts — single and multi post_select fields
  • team_members — repeater containing text, textarea, image, and a group for social URLs
  • nested_repeater — three-level nested repeater demonstrating sub-nested data structures

The Hero block is the canonical reference for nested repeaters. Use it as a starting point when you need hierarchical repeater data in your own blocks.

Blocks/Menu/ is a two-row site header with a keyboard-accessible Alpine.js live-search overlay. It extends TAW\Core\Block (UI Block) and renders itself with no metabox configuration required.

// header.php
use TAW\Blocks\Menu\Menu;

(new Menu())->render();

What it includes:

  • Top row — custom logo (or site name fallback), optional company address pulled from OptionsPage, and a search trigger button.
  • Bottom rowprimary nav menu with hover dropdowns for items that have children, plus optional company email and phone from OptionsPage.
  • Search overlay — debounced live fetch against GET /wp-json/wp/v2/search, a results list, empty state, loading spinner, Escape-to-close, and body scroll lock.

The block reads company contact details (company_address, company_email, company_phone) directly from your OptionsPage settings, so those fields must be configured under TAW Settings in the admin for them to appear.

Restrict search to specific post types

Edit the subtype param in Blocks/Menu/script.js:

const params = new URLSearchParams({
    search: query,
    type: 'post',
    subtype: 'post,page', // ← adjust as needed
    per_page: 8,
});

Translate overlay labels

Edit the strings directly in Blocks/Menu/index.php:

  • What are you looking for? — the overlay heading
  • Search posts and pages… — the input placeholder

The Menu block pulls primary navigation from the location registered in functions.php via register_nav_menus(). Make sure a menu is assigned to the primary location in WordPress Admin → Appearance → Menus before rendering.

Framework updates

TAW Core is distributed as a Composer package (taw/core). To update the framework to the latest version, run:

composer update taw/core

Static analysis

TAW Theme ships phpstan.neon (level 5, Blocks/ and inc/ only) with WordPress core stubs via szepeviktor/phpstan-wordpress, so core functions/classes resolve correctly instead of erroring as unknown.

composer run phpstan

Runs automatically in CI on every push/PR. Prefer fixing types over adding ignoreErrors entries — see the wp-phpstan skill in WordPress/agent-skills for WordPress-specific typing patterns.

Direct field read/write — fields:get / fields:set

php bin/taw fields:get/fields:set read or write a single Metabox/OptionsPage field's value directly — the same primitive VisualEditorEndpoint uses for its REST-driven saves, exposed as a CLI command. The field's type is resolved from the live Metabox registry (the same one inspect reports), then dispatched to the matching type-aware getter/sanitizer, so a repeater comes back as a decoded array and gets sanitized exactly like a real admin form save — not a hand-rolled approximation.

php bin/taw fields:get 42 hero_heading --json
php bin/taw fields:get 42 team_members --json                        # repeater comes back as a decoded array

php bin/taw fields:set 42 hero_heading "Welcome"
php bin/taw fields:set 42 team_members --file=/tmp/team.json         # --file avoids shell JSON-quoting for repeater/array values
php bin/taw fields:set 42 hero_heading "Welcome" --dry-run           # preview the sanitized result without writing

For a group sub-field, use the compound ID exactly as inspect reports it (e.g. hero_cta_text for the cta_text sub-field of a hero_cta group). This doesn't upload media — an image/files field value must already be a valid attachment ID — and it doesn't check whether the field's owning block is actually attached to the target post's type/template, same trust model as any other direct WordPress data write.

This is the "populate the content" half of a design-to-page pipeline: figma-to-block/make-metablock scaffold the block and its empty fields, fields:set fills them in.

Content-writing safety model

fields:set itself has no interactive confirmation — it's a raw primitive, usable from scripts, not just an AI agent. All risk judgment belongs to whichever skill or agent calls it. The populate-content Claude Code skill (and the population step built into make-metablock/figma-to-block/build-page) follows a mandatory model: always --dry-run before a real write; always confirm before overwriting a non-empty existing value (old vs. new shown side by side); always confirm before writing to a publish-status post regardless of whether the field is empty; one confirmation for an entire batch plan, never silently expanded mid-batch; extra scrutiny on wysiwyg/'sanitize' => 'code' fields when the source content is external; never touches post_title/post_content/post_status, only Metabox/OptionsPage meta; no wildcard mass-edits — every affected post/field is enumerated up front.

populate-content is security-sensitive by design — it writes directly to a live database. Skipping the dry-run or the confirmation gate because a change "looks small" is exactly the failure mode this model exists to prevent.

See the AI Integration page for the full picture — skills, this safety model, inspect, WP-CLI, CI, and static analysis together.

WP-CLI — live site data access

bin/taw scaffolds, introspects, and (via fields:get/fields:set above) can now read/write individual field values — but it has no general-purpose view into site content beyond that. For posts, options, users, terms, transients, arbitrary PHP via wp eval/wp eval-file, or an interactive wp shell, use WordPress's own official CLI, WP-CLI (the wp command). It's a host-level tool, not bundled by this theme, but present on virtually every real WordPress host and every local dev environment (Local, Herd, DDEV).

Prefer php bin/taw wp <args> over a bare wp command — a thin passthrough (every argument forwarded exactly as given) that automatically resolves --path and, under Local by Flywheel, the per-site MySQL socket (see the Callout below) — no manual configuration needed:

php bin/taw wp post list --post_type=page --fields=ID,post_title,post_status
php bin/taw wp option get siteurl
php bin/taw wp eval 'echo home_url();'

Local by Flywheel connection quirk: wp-config.php's DB_HOST is localhost, but Local runs a per-site MySQL instance on its own Unix socket, not the system default. A bare wp command fails with Error establishing a database connection even though the site works fine in the browser — php bin/taw wp (above) resolves this automatically. If you need the real wp binary directly anyway, the manual fix is the same thing bin/taw wp does internally:

ps aux | grep mysqld   # find the socket path for this site's mysqld — .../Local/run/<SITE_ID>/conf/mysql/my.cnf
SOCK="$HOME/Library/Application Support/Local/run/<SITE_ID>/mysql/mysqld.sock"
php -d mysqli.default_socket="$SOCK" -d pdo_mysql.default_socket="$SOCK" "$(which wp)" post list --path=/path/to/site/app/public

If multiple Local sites are running, disambiguate sockets with mysql --socket="$SOCK" -uroot -proot -e "SELECT option_value FROM wp_options WHERE option_name='siteurl';" local.

ViteLoader::isDevServerRunning() is safe under WP-CLI — the dev-server probe only runs on wp_enqueue_scripts, which doesn't fire for most wp commands. For general WP-CLI operations beyond this framework's scope, see the wp-wpcli-and-ops skill in WordPress/agent-skills.

Theme updates (auto-updater)

TAW\Core\ThemeUpdater (shipped in taw/core) hooks into WordPress's update system to check a GitHub Releases URL for new versions. When a newer release is found, the standard Update Available notice appears in the admin.

// functions.php
new TAW\Core\ThemeUpdater([
    'slug'       => 'taw-theme',
    'github_url' => 'https://api.github.com/repos/your-username/taw-theme/releases/latest',
]);

Updates are cached for 6 hours to avoid GitHub rate limits. The updater prefers a built ZIP asset in the release; falls back to GitHub's auto-generated zipball.

CSS Studio (dev-only visual editor)

CSS Studio is a browser-based visual editor that streams live-page edits directly to your AI coding assistant. It is pre-installed in TAW Theme.

Requires: Vite dev server running (npm run dev) and the toggle enabled in WP Admin.

Toggle: WP Admin → TAW Settings → Developer Tools → Enable CSS Studio

Start a session (inside Claude Code / your AI agent):

/studio

When active, every change you make in the visual panel — text edits, style tweaks, attribute changes — is sent to the agent as structured data and applied to the source files automatically. CSS Studio is injected only when the Vite dev server is active, so it never ships to production.

Deepen your theme integration with these focused guides: