A modern, performance-first WordPress starter theme powered with Vite. It is designed from the ground up to score well on Core Web Vitals, works out of the box with ACF (Advanced Custom Fields), and includes ready-to-uncomment integration points for Yoast SEO and Gravity Forms.
- Features
- Requirements
- Getting Started
- Build System (Vite)
- Project Structure
- Performance Optimizations
- ACF Integration
- ACF Blocks
- Yoast SEO Notes
- Gravity Forms Notes
- Theme Customizer
- Personalized Style for Admin Login Page
- License
- β‘ Vite-powered build pipeline β SCSS/JS compilation, code splitting, source maps, and static asset copying.
- π Performance-first architecture β critical CSS inlining, async/preloaded stylesheets, deferred/module JS, idle-loaded analytics.
- πΌοΈ Automatic WebP conversion for every uploaded JPG/PNG, served through a single, simple helper function.
- π§© ACF-ready β Some kind of "Gutenberg style" for ACF blocks, with per-block asset loading.
- π Privacy/performance-conscious GTM implementation loaded only when the browser is idle or the user interacts with the page.
- π§Ή WordPress cleanup out of the box β disabled emojis, shortlinks, RSD/WLW links, comments support, XML-RPC clutter, and more.
- π Custom admin login screen (
my-admin/). - π οΈ Sensible defaults for Customizer (logo, social links), custom widgets, nav menus, and accessible (WCAG) menu markup.
- WordPress 6.x+
- PHP 8.0+ (uses modern syntax such as
str_contains,str_ends_with, arrow functions) - Node.js 18+ and npm
- Advanced Custom Fields PRO (required β the theme's blocks and Theme Settings depend on it)
- Optional: Yoast SEO, Gravity Forms
-
Copy/clone the theme into
wp-content/themes/theme-name/. -
Install front-end dependencies:
npm install
-
Run the dev/watch build:
npm run dev
-
Build production assets:
npm run build
-
Activate the theme in WordPress Admin β Appearance β Themes.
-
Install and activate ACF PRO, then sync the included block field groups (see ACF Integration).
The theme does not rely on WordPress' native asset pipeline for compilation β everything is bundled with Vite (vite.config.js), including a legacy build plugin, Sass (Dart Sass, modern-compiler API), Autoprefixer, and Terser for production minification.
Key points of the Vite configuration:
- Output stays inside the theme root (
outDir: '.'), so compiled assets can be enqueued directly by PHP with no separate deploy step. - Fixed entry points compile to predictable paths:
src/scss/critical.scssβdist/css/critical.csssrc/scss/general.scssβdist/css/general.csssrc/scss/editor-styles.scssβdist/css/editor-styles.csssrc/scss/pages/404.scssβdist/css/pages/404.csssrc/js/general.jsβdist/js/general.jssrc/js/analytics.jsβdist/js/analytics.js
- Dynamic block entries β
buildBlockEntries()scanssrc/scss/blocks/*andsrc/js/blocks/*and automatically generates a Vite entry for each file, so every new block file gets its ownblocks/{name}/block-style.cssandblocks/{name}/block-script.jswithout touching the config again. - Static assets (
src/images/) are copied todist/images/viavite-plugin-static-copy. - Aliases:
@βsrc/,~βnode_modules/(for legacy~packageSCSS imports). - Source maps are enabled only in development; Terser minification (with
drop_console: false) is used for production.
npm scripts:
| Script | Description |
|---|---|
npm run dev / npm run watch |
Watches and rebuilds assets in development mode |
npm run build / npm run production |
Produces the optimized production build |
sz-starter/
βββ src/ # Source files compiled by Vite
β βββ scss/ # Stylesheets (critical, general, blocks, gutenberg, vendorβ¦)
β βββ js/ # Scripts (general.js, analytics.js, blocks/β¦)
β βββ images/ # Static images (copied as-is to dist/images)
βββ dist/ # Compiled/production assets (generated, do not edit by hand)
βββ blocks/ # ACF Blocks (each with block.json, block.php, preview.php, compiled assets)
β βββ full-image/
β βββ swiper-carousel/
β βββ contact/
β βββ vertical-space/
β βββ Theme Settings - GTM Code.json # ACF field group export for Theme Settings (Options page)
βββ template-parts/ # Reusable template partials (header, footer, archive, iconsβ¦)
βββ inc/ # Theme functionality, split by concern
β βββ helper-functions.php # rrp(), WebP generation, logo, SVG inlinerβ¦
β βββ customizer.php # Theme Customizer bootstrap
β βββ customizer/ # Individual Customizer sections (logo, social links)
β βββ custom-widgets.php # Widget registration
β βββ custom-widgets/ # Individual widgets (social links, footer widgets)
β βββ custom-post-types.php # CPT registration (opt-in, examples included)
β βββ wordpress-cleanup.php # Menu/post class cleanup, excerpt, comment form tweaks
β βββ body-class.php # Device/browser/OS classes on <body>
β βββ ajax-calls.php # AJAX endpoint handlers
β βββ tinymce.php # Classic editor style formats
β βββ acf-blocks.php # Registers every folder in /blocks as an ACF Block
βββ my-admin/ # Custom wp-login.php styling/behavior
βββ favicon/ # Favicon set + site.webmanifest
βββ functions.php # Theme bootstrap: enqueues, theme supports, cleanup hooks
βββ header.php / footer.php # Global document shell (critical CSS inlined here)
βββ index.php / page.php / single.php / archive.php / search.php / 404.php / comments.php
βββ theme.json # Gutenberg / Global Styles configuration
βββ style.css / style-rtl.css # Theme stylesheet header (required by WordPress) + RTL support
βββ vite.config.js / package.json
This theme's central goal is speed. Nearly every decision in functions.php and inc/helper-functions.php exists to reduce render-blocking resources and unnecessary work.
- Critical CSS is inlined directly in
<head>(header.phpreadsdist/css/critical.csswithfile_get_contents()and prints it inside a<style>tag), so above-the-fold content never waits on an external stylesheet request. - Non-critical stylesheets are loaded asynchronously. Any style handle ending in
-styles(global styles, block styles,404-styles, and optionally Gravity Forms handles) is rewritten via thestyle_loader_tagfilter into arel="preload" ... onload="this.rel='stylesheet'"pattern, with a<noscript>fallback for JS-disabled browsers. - All JS is deferred.
general.jsandanalytics.jsare enqueued with the native WordPress['strategy' => 'defer', 'in_footer' => true]script strategy. jQuery itself is de-registered and re-registered as deferred (only on the front end). - Scripts are output as ES Modules. Any script handle ending in
-scriptsgetstype="module"injected via thescript_loader_tagfilter, matching Vite's native ESM output. - Per-block asset loading.
register_blocks_assets()walks the parsed blocks of the current post and only enqueues the CSS/JS for the ACF blocks actually present on the page (withfilemtime()-based cache busting and de-duplication), instead of loading every block's assets on every page. - The polyfill scripts (
wp-polyfill,regenerator-runtime) are deregistered to shave extra weight for modern browsers. - Fonts are preconnected and preloaded (see
template-parts/general/head-fonts.php) using thepreload+onloadswap-to-stylesheet trick to avoid blocking rendering. - Output is minified server-side too β a
template_redirectoutput buffer strips leading whitespace/empty lines from the final HTML before it's sent to the browser.
inc/helper-functions.php hooks into wp_generate_attachment_metadata (admin-only, so it never impacts front-end performance) to automatically generate a .webp version of every uploaded JPG/PNG, for the original file and every registered intermediate size. PNGs are converted with alpha channel preservation; JPGs use the native WordPress image editor. Quality is set to 82 for both formats. A matching delete_attachment hook removes the generated WebP files when the original attachment is deleted, keeping the uploads folder clean.
rrp() ("render responsive picture") is the theme's single entry point for outputting optimized, responsive images. Given one or two ACF image IDs (desktop and optionally mobile), it:
- Automatically serves the pre-generated
.webpfile when available, with the original format as a fallback (<picture>+<source type="image/webp">pattern). - Builds
srcsetfor both formats using WordPress' native attachment metadata. - Supports independent desktop/mobile sources with a
768pxbreakpoint when a mobile image is supplied. - Exposes simple options (
picture_class,img_class,loading,fetchpriority,decoding) with sensible lazy-loading defaults, while still allowing e.g.fetchpriority: highfor above-the-fold hero images.
Usage example inside a block or template:
//$fields['desktop_image'], $fields['mobile_image'] must return image ID ($fields['desktop_image'] can be replaced by get_post_thumbnail_id($post->ID) if you need the featured image of a post/page )
rrp( $fields['desktop_image'], $fields['mobile_image'] ?? null, [
'picture_class' => 'bg',
'img_class' => 'cover',
'loading' => 'lazy',
] );src/js/analytics.js implements a lazy-loading strategy for Google Tag Manager designed to protect Core Web Vitals (particularly TBT/INP):
- GTM is not injected on page load. It's only fetched when the browser reports
requestIdleCallback(with a3000mstimeout fallback) or as soon as the user actually interacts with the page (scroll,mousemove,touchstart,clickβ whichever fires first, each listener isonceandpassive). - The GTM container ID is not hardcoded: it's injected from ACF's Theme Settings options page (
window.GTM_ID, set inheader.php) so it can be managed entirely from the WordPress admin. - A separate
gtm_body_codeACF field allows outputting the<noscript>GTM body snippet right afterwp_body_open(). - A
cookiebot_codeACF field is also available for consent-management scripts, output in<head>.
- Emoji detection scripts/styles, DNS-prefetch hints, shortlinks, RSD, WLW manifest, and the WordPress generator meta tag are all removed from
<head>. - Comments are fully disabled (admin menu, admin bar, post type support, comment feeds,
comments_open/pings_openfilters) since most marketing/business sites don't use them β reducing both database queries and surface area. - The old Tag widget/taxonomy is disabled by default on posts (
register_taxonomy('post_tag', [])). - The theme excludes itself from WordPress.org theme update checks (useful for custom/proprietary themes with a name collision).
This theme is built around Advanced Custom Fields and assumes ACF (ideally ACF PRO) is active:
inc/acf-blocks.phpautomatically registers every folder inside/blocksas a native ACF Block viaregister_block_type()β no manual registration needed per block. To add a new block, just create a new folder with ablock.json+block.phpand it's picked up automatically oninit.- ACF field groups are saved as JSON in the stylesheet directory (
acf/settings/save_jsonfilter points to/acf-json), making field groups portable and version-controllable. When cloning this theme for a new project, sync the JSON field groups from the ACF admin screen. - The included
Theme Settings - GTM Code.jsonis an ACF field-group export for a Theme Settings options page, containing the Cookiebot code, GTM container ID, and GTM body<noscript>snippet fields referenced inheader.php. - Each block ships with its own
preview.phpfor a friendlier editor experience; if a block doesn't define one, the theme falls back totemplate-parts/parts/acf-block-preview.php. - Helper functions such as
rrp()andinline_svg_from_media()are designed to consume ACF Image/File field values (attachment IDs) directly.
Included starter blocks (all under /blocks):
| Block | Description |
|---|---|
full-image |
Full-width responsive image/banner block using rrp(), with an optional caption. |
swiper-carousel |
Carousel block powered by Swiper. |
contact |
Contact section block (pairs well with Gravity Forms β see below). |
vertical-space |
Utility spacer block for controlling space between sections. |
Each block folder follows the same convention:
blocks/{name}/
βββ block.json # Block registration + ACF config (renderTemplate)
βββ block.php # PHP render callback
βββ preview.php # Editor-only preview markup
βββ block-style.css # Compiled from src/scss/blocks/{name}.scss
βββ block-script.js # Compiled from src/js/blocks/{name}.js
Add a new block by creating a new folder (with block.json + block.php) and, if needed, matching src/scss/blocks/{name}.scss / src/js/blocks/{name}.js source files β Vite and acf-blocks.php will pick everything up automatically on the next build.
The theme is compatible with Yoast SEO out of the box (no conflicting title tags, since add_theme_support('title-tag') lets Yoast/WordPress manage <title>). functions.php includes commented-out snippets for common Yoast tweaks that can be enabled per project, such as disabling the SearchAction schema piece or forcing the WordPress search page to 404 (useful on sites where Yoast handles all indexable content and a native search UX isn't desired).
Several commented-out integration points are included in functions.php for projects using Gravity Forms:
- Swapping the default AJAX spinner for the theme's own SVG loader (
dist/images/svg/ajax-loader.svg). - Adding a
bodyclass while a form is submitting/validating (useful for loading states in CSS). - Disabling the default confirmation-page anchor scroll.
- Dispatching a custom
formRenderedevent, handy when a project uses a custom<select>library (e.g. Tom Select, already included as a dependency) on Gravity Forms fields. - Async-loading Gravity Forms' own stylesheets by adding their handles to the
$async_handlesarray insz_theme_global_enqueues().
Uncomment and adapt whichever snippets a given project needs.
inc/customizer.php provides small factory helpers (add_section_text_field, add_section_image_field, add_section_link_field, add_section_color_field, add_section_select_field, etc.) to speed up adding new Customizer controls, plus two ready-made sections:
- Site Logo (
inc/customizer/site-logo.php) β powershtml_site_logo()ininc/helper-functions.php. - Social Links (
inc/customizer/social-links.php) β powers the bundledCustom_Social_Linkswidget and social icon template parts (template-parts/icons/*.php: Facebook, Instagram, LinkedIn, Twitter).
my-admin/login.php (loaded via locate_template() in functions.php) replaces the default wp-login.php branding with theme-specific styling (login.css) and behavior (login.js), including a themed favicon for wp-admin.
If you prefer the classic login, you can comment the line in functions.php to bring back the classic style for login page.
Licensed under the GNU General Public License v2 or later, in line with WordPress' own licensing β see the LICENSE file for details.