Overview#
Launch is a complete marketing website for a software product, built as a WordPress block theme.
The fictional brand is Launch, a calm workspace for roadmaps, releases and customer feedback. The
theme has a home page with a product screenshot built in HTML, a features page, a pricing page with
a monthly/annual switch, a comparison table and an FAQ, an about page with a timeline, team and
careers, a contact page with a validated form, a sign-in / sign-up page and a 404 page, in light and
dark themes. It is version 1.0.0 (see template.json and style.css), works with WordPress 7.1 and
PHP 7.4 or newer, and is the WordPress version of the HTML template of the same name. See it live at
freeframe.dev/templates/launch.
Everything you see is made of core WordPress blocks arranged in block patterns, so you edit text,
links, buttons, lists and images in the block editor without touching code. Colours, the font and
font sizes are design tokens in theme.json: change them in Appearance > Editor > Styles and the
whole site follows.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
launch.zipfrom freeframe.dev/templates/launch (the WordPress tab). - In your WordPress admin go to Appearance > Themes > Add New Theme > Upload Theme, choose the zip and click Install Now, then Activate. The theme is listed as Freeframe Launch.
- Go to Appearance > Launch setup and click Create the demo pages. This creates the Home, Features, Pricing, About, Contact and Sign in pages from the theme's page patterns, sets Home as the front page (Settings > Reading) and creates the header menu. Pages that already exist with the same slug are left alone, so it is safe to click it again.
- Visit your site. Edit any page in Pages, and the header, footer and templates in Appearance > Editor.
You can also try the theme without installing anything: the Try it in WordPress button on freeframe.dev opens it in WordPress Playground (WordPress running in your browser) with the demo pages already set up.
To work on the theme's files locally you need Node.js 24 and pnpm; WordPress itself runs in WordPress Playground, so there is no PHP, web server or database to install:
pnpm install
pnpm dev # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400pnpm dev signs you in as admin (password password) and creates the demo pages. The site is
rebuilt from scratch every time you start it; your edits to theme files are live, edits made in the
WordPress admin are not kept.
Pages#
Each page is a page pattern (in Patterns > Launch pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Launch.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (hero and product screenshot), logo-cloud, feature-grid, command-menu, stats (count-up numbers), testimonials, pricing-summary, cta |
| Features | /features/ |
page-features.php |
features-hero, features-nav (links to each section), feature-roadmaps, feature-issues, feature-releases, feature-feedback, feature-insights, integrations, security, features-cta |
| Pricing | /pricing/ |
page-pricing.php |
pricing-plans (monthly/annual switch, plans, enterprise), pricing-compare (comparison table), faq, cta |
| About | /about/ |
page-about.php |
about-hero, story (story, numbers and timeline), values, team, careers, about-cta |
| Contact | /contact/ |
page-contact.php |
contact-hero, contact-form (form, other ways to reach us, offices) |
| Sign in | /login/ |
page-login.php |
sign-in (sign-in and create-account tabs) |
| 404 | any missing URL | templates/404.html |
404 |
Posts use templates/single.html; the blog, archives and search results use templates/index.html,
templates/archive.html and templates/search.html.
Project structure#
freeframe-launch/
style.css theme header (name, version, requirements, license); no styles
theme.json design tokens: palette, fonts, font sizes, spacing, layout widths, button
functions.php loads the files in inc/
inc/setup.php stylesheet and script, editor styles, dark mode script, pattern categories
inc/icons.php registers assets/icons/*.svg for the Icon block
inc/icon-labels.php the icons' names shown in the editor
inc/block-styles.php button, list and group block styles
inc/site-setup.php Appearance > Launch setup: creates the demo pages and the menu
templates/ page.html (pages), page-with-title.html, single.html, index.html,
archive.html, search.html, 404.html
parts/ header.html and footer.html (they show the header and footer patterns)
patterns/ every section and page (PHP files with block markup)
assets/css/theme.css the compiled stylesheet (do not edit: built from src/)
assets/css/editor.css the same for the block editor (generated by src/editor-css.mjs)
assets/js/main.js the site's JavaScript
assets/icons/ SVG icons for the Icon block
assets/img/ logo, favicon, social image, portraits (people/)
assets/fonts/inter/ the Inter font files and their license (OFL.txt)
src/theme.css the Tailwind CSS source of assets/css/theme.css
src/icons.css icons drawn before or after text with CSS (generated)
src/editor-css.mjs writes assets/css/editor.css after each build
languages/ freeframe-launch.pot, the template for translations
blueprint.json WordPress Playground setup used by `pnpm dev`
screenshot.png the picture shown in Appearance > Themes
package.json the CSS build and the local WordPress (Node.js tooling)
readme.txt, README.md, DOCS.md, CHANGELOG.md, LICENSE.txtCustomising#
Brand and colours#
The colours are in theme.json (settings.color.palette) and in Appearance > Editor > Styles >
Colors > Palette:
| Colours | Used for |
|---|---|
| Brand 50 to Brand 950 | the purple of buttons, links, eyebrows, icons, badges and focus rings |
| Zinc 50 to Zinc 950, White, Black | neutrals, for your own blocks |
| Base and Contrast | the page background and text; they switch to dark values in dark mode |
The theme's stylesheet reads the Brand colours from the palette, so changing Brand 600 in Styles
recolours every primary button, link and accent at once. Pick a full ramp of shades (light 50 to
dark 950) so hover states and dark mode keep their contrast. In theme.json:
{ "slug": "brand-600", "name": "Brand 600", "color": "#6233ee" }The neutrals used by the design itself (greys, the dark background) come from Tailwind's zinc scale
in the stylesheet; the Zinc palette entries are the same colours for your own blocks. The dark mode
values of Base and Contrast are set in src/theme.css (html.dark { --wp--preset--color--base: ... }).
Typography#
The font is Inter (variable, weights 400 to 800), bundled in assets/fonts/inter/ and declared in
theme.json (settings.typography.fontFamilies), so no request goes to Google. To use another
font, install it in Appearance > Editor > Styles > Typography > Fonts (the Font Library) and
select it for Text, or replace the fontFace entries in theme.json. The theme's utility
classes use the font chosen for the site (--wp--preset--font-family--inter), so rename that slug
only together with src/theme.css.
Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The
stylesheet uses them too: changing Large in Styles changes every text-lg in the design.
Content#
Edit pages in Pages: click any text to change it, select a button to change its link, select an image and use Replace. Sections are groups; open List View (the stacked lines icon at the top left) to select a section, move it, duplicate it or delete it. To add a section from another page, use the block inserter (+) > Patterns > Launch.
Some content is in Custom HTML blocks, because blocks can't express it: edit those in the block's HTML view.
| Pattern | Custom HTML | What it is |
|---|---|---|
home-hero |
the product screenshot | an illustration built in HTML; replace it with an Image block to show a real screenshot |
command-menu |
the command menu | illustration |
feature-roadmaps, feature-issues, feature-releases, feature-feedback, feature-insights |
the picture beside each feature | illustrations |
pricing-plans |
the Monthly / Annual switch | a switch read by assets/js/main.js |
pricing-compare |
the comparison table | a table with icons and a scroll area on small screens |
contact-form |
the form and its thank-you message | see Forms |
sign-in |
the sign-in / create-account card | tabs and two forms |
header |
the dark mode switch and the menu button | buttons driven by assets/js/main.js |
A few blocks carry a class (Block settings > Advanced > Additional CSS class(es)) that the script or stylesheet reads:
count-upon a paragraph animates its number when it scrolls into view. Write the number as it should end up (4,000+,2.3×,99.99%,38 ms): the digits, decimals and suffix are read from the text.billing-monthlyandbilling-annualon the two groups inside each pricing card: the switch shows one of them. In the editor both are shown, the annual one marked "Annual".reveal(andreveal-delay-1toreveal-delay-3) fades a block in as it scrolls into view.link-cardon a group makes the whole group clickable: the first link inside it stretches over the group.
To add a pricing plan, duplicate a plan card in List View, then change its name, description, both prices, the button and the list.
Images and artwork#
- Portraits (testimonials, team, sign-in quote) are SVG illustrations in
assets/img/people/, shown with Image blocks. Replace them with photos from the Media Library (square, at least 256 × 256 px); the round crop comes from the block's classes. - Logo: the header and footer show
assets/img/logo.svgin an Image block next to the Site Title block. Replace the image in Appearance > Editor > Patterns > Header (and Footer); the name comes from Settings > General > Site Title. - Icons: icons are Icon blocks. Select one and pick another icon from the Launch collection
(the theme's icons) or WordPress's own. Icons inside a link or button (the arrows) are drawn by
CSS: the class
icon-after-arrow-righton the block adds one; the available names are insrc/icons.css. - Customer logos on the home page are Icon blocks with a paragraph; replace them with Image blocks to show real logos.
- Illustrations (the product screenshot and the feature pictures) are Custom HTML; see Content.
- Social image:
assets/img/og-image.png(1200 × 630, sourceog-image.svg). WordPress doesn't output Open Graph tags by itself; see SEO and sharing. - Favicon:
assets/img/favicon.svgis used until you set a Site Icon in Settings > General.
Navigation, header and footer#
The header and footer are template parts (parts/header.html, parts/footer.html) showing the
header and footer patterns. Edit them in Appearance > Editor > Patterns > Header / Footer.
The header has two Navigation blocks showing the same menu: the desktop one and the one in the mobile menu. Edit the menu once in Appearance > Editor > Navigation (the setup creates "Main menu" with Features, Pricing, About and Contact). A Navigation block that doesn't point at a menu shows the most recently created one; to pin a menu, select the Navigation block and choose it in the block settings, for both blocks.
The footer's link columns are List blocks: click a link to change it, press Enter to add one.
Dark mode#
The switch in the header toggles the dark class on <html> and remembers the choice in
localStorage (key theme, value dark or light). Without a saved choice the visitor's system
setting decides. A small script printed first in <head> (freeframe_launch_theme_script() in
inc/setup.php) applies the class before the page is drawn, so there is no flash.
To always show the light theme, remove the switch from the header pattern and add this to a child
theme's functions.php:
add_filter( 'freeframe_launch_dark_mode', '__return_false' );Dark mode styles are the dark: classes on blocks; colours picked in the editor's colour panels
are the same in both modes, except Base and Contrast.
Features#
Sticky header and mobile menu#
The header sticks to the top and gains a border and shadow once the page scrolls (is-scrolled,
set by assets/js/main.js). Below 768 px the menu button (#menu-button) opens #mobile-menu; it
updates aria-expanded and its label, closes on Escape (returning focus to the button), on a link
click and when the window grows past 768 px.
Scroll reveal and count-up numbers#
Blocks with the reveal class fade and slide in when they enter the viewport (IntersectionObserver
in assets/js/main.js). They are only hidden once JavaScript runs (html.js), so the content is
there without it. Paragraphs with count-up count from zero to their number. With reduced motion
enabled, both show their final state immediately.
Pricing switch#
The Monthly / Annual switch is a role="switch" button (data-billing-toggle). It sets
aria-checked, and the script adds ff-annual to <html>, which shows the billing-annual groups
instead of the billing-monthly ones. Clicking the "Monthly" and "Annual" labels works too.
Comparison table#
The table in pricing-compare scrolls sideways on small screens; the scroll area is focusable and
labelled, so keyboard users can scroll it. Ticks and dashes have hidden text ("Included", "Not
included") for screen readers.
FAQ#
The FAQ is WordPress's Accordion block: add a question with the + button in List View or by
duplicating an item. Each question is a heading with a button (aria-expanded, aria-controls)
and the answer is hidden until opened, and found by the browser's find-in-page. The look (chevron
in a circle, dividers) is styled for every Accordion block in src/theme.css.
Sign in and create account tabs#
The card on the Sign in page uses the ARIA tabs pattern: arrow keys move between the tabs, and a
link to /login/#signup opens the Create account tab. The Show / Hide button next to each
password switches the field type and aria-pressed.
Back to top#
A button appears after scrolling about one screen down; it scrolls to the top (instantly with
reduced motion) and moves keyboard focus to the main content. It is created by
assets/js/main.js; to remove it, delete the "Back to top" part of that file and the
.back-to-top styles in src/theme.css, then run pnpm build.
Block styles#
In the block settings' Styles panel:
| Block | Styles |
|---|---|
| Button | Fill (default, the primary purple), Secondary, Dark, White (on dark), Outline (on dark), Pill, Plain link, Icon only (WordPress's own Outline style is replaced by Secondary) |
| List | Checklist (a tick before each item) |
| Group | Grid and glow backdrop, Glow |
Add the class is-large to a Button block for the large size.
Forms#
The contact form, the sign-in form and the create-account form check what visitors type in the
browser (form[data-validate] in assets/js/main.js): required fields, email format, minimum
length, and the consent box. Errors appear under each field and focus moves to the first one.
When everything is valid the form is replaced by its thank-you message. Nothing is sent
anywhere: they are demo forms until you connect them.
Option 1: a form plugin#
The usual way on WordPress. Install a form plugin (WPForms, Gravity Forms, Fluent Forms, Contact
Form 7, Jetpack Forms...), build the form there, then on the Contact page delete the two Custom
HTML blocks of the form and its thank-you message and insert the plugin's block or shortcode in
their place. The plugin sends the messages and stores them. To make its fields look like the
theme's, add the theme's classes where the plugin allows custom classes: input on fields, label
on labels, btn btn-primary on the submit button.
Option 2: a form service#
Keep the theme's form and send it to a service such as Formspree:
-
Create a form on the service and copy its endpoint URL.
-
Edit the Contact page, open the form's Custom HTML block and add the endpoint to the form tag:
html<form id="contact-form" data-validate data-success="contact-success" novalidate action="https://formspree.io/f/your-id" method="post" class="space-y-6" aria-describedby="form-note"> -
In
assets/js/main.js, replace the body ofonValid()so it posts the form and only shows the thank-you message when the service accepts it:jsfunction onValid(form) { var success = document.getElementById(form.dataset.success); var button = form.querySelector('[type="submit"]'); button.disabled = true; fetch(form.action, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } }) .then(function (res) { if (!res.ok) throw new Error('Request failed'); form.hidden = true; if (success) { success.hidden = false; success.focus(); } }) .catch(function () { button.disabled = false; alert('Sorry, the message could not be sent.'); }); }Make this change in a child theme (copy
assets/js/main.jsthere and load it instead), or it will be overwritten when you update the theme.
The sign-in and create-account forms are for show: a real product signs people in on its own application. Point their Custom HTML at your app's sign-in page, or replace the card with links.
Saved data (localStorage)#
| Key | Value | Used for |
|---|---|---|
theme |
"dark" or "light" |
the visitor's dark mode choice; any other value is ignored and the system setting is used |
Clear it from the browser's developer tools (Application > Local Storage) or by switching the theme back.
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Pricing — Launch"); the home page shows the site name and tagline (Settings > General). The
theme doesn't output meta descriptions or Open Graph tags; use an SEO plugin such as Yoast SEO,
Rank Math or The SEO Framework, and give it assets/img/og-image.png (or your own 1200 × 630 image)
as the default social image. WordPress already generates a sitemap at /wp-sitemap.xml.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks
(header, navigation with labels, main, footer), one h1 per page, visible focus rings, a labelled
dark mode switch with aria-pressed, the mobile menu button with aria-expanded, accessible
tabs, accordion and switch, form errors announced through aria-describedby and aria-invalid,
alt text on portraits, and reduced-motion support for every animation. The demo passes axe
(WCAG 2.1 A and AA) in light and dark mode.
When you edit: keep one Heading 1 per page, give images alt text (or mark them decorative), keep
link text meaningful, and label any field you add to a form (<label for="...">).
Building and deploying#
You only need this if you change the theme's files. The stylesheet assets/css/theme.css is
compiled with Tailwind CSS from src/theme.css and the classes used in patterns/, parts/,
templates/, inc/ and assets/js/:
pnpm install
pnpm build # compile assets/css/theme.css and assets/css/editor.css
pnpm watch # recompile assets/css/theme.css on every change (run pnpm build before you commit)Run pnpm build after adding a utility class to a pattern or template, otherwise the class has no
style. Classes you type in the editor's Additional CSS class(es) field are only styled if the
stylesheet already contains them.
To deploy, zip the theme folder (named freeframe-launch, without node_modules/) and upload it
in Appearance > Themes, or copy the folder to wp-content/themes/ on your server.
Translations#
The theme is translation-ready with the text domain freeframe-launch. All text in patterns,
templates and PHP goes through WordPress's translation functions; the script's strings
(button labels, form errors) are translated in PHP (freeframe_launch_script_strings() in
inc/setup.php). Create a translation with Loco Translate, or with WP-CLI and Poedit:
wp i18n make-pot wp-content/themes/freeframe-launch wp-content/themes/freeframe-launch/languages/freeframe-launch.potSave freeframe-launch-de_DE.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Launch setup is copied into your pages in the site's
language at that moment; translate it in the editor like any other content.
Child themes#
Use a child theme for changes you want to keep when Launch is updated. Create
wp-content/themes/launch-child/style.css:
/*
Theme Name: Launch Child
Template: freeframe-launch
*/Templates, parts, patterns and theme.json in the child theme override the parent's files of the
same name. The parent's stylesheet only contains the classes the parent uses: if your child theme's
patterns use new utility classes, compile a stylesheet for the child (copy package.json and
src/theme.css, point the @source lines at the child's folders) and enqueue it.
Troubleshooting#
- The pages look unstyled or new classes do nothing. The stylesheet only contains classes it
found when it was built. Run
pnpm buildafter editing patterns, then reload. - "This block contains unexpected or invalid content". A pattern or page was edited by hand with markup that doesn't match the block. Click Attempt recovery, or re-insert the section from Patterns > Launch.
- The header menu shows other pages, or an old menu. A Navigation block without a chosen menu shows the most recent one. Select it and choose "Main menu" (or yours) in the block settings, in both the desktop and the mobile Navigation block.
- Launch setup says the pages exist, but they are empty. It never overwrites existing pages. Delete (and empty the trash of) the pages with those slugs, or open each page and insert its page pattern from Patterns > Launch pages.
- The forms don't send anything. They are demo forms; see Forms.
- The theme can't be installed on an older WordPress. Launch uses the Icon block's icon collections from WordPress 7.1; update WordPress first.
- A page shows the title twice or no title. Pages use a template without a title, because the page patterns start with their own heading. For a plain text page (privacy policy, terms), choose the Page with title template in the page's settings.
License#
MIT, which is compatible with the GPL that WordPress requires (see LICENSE.txt in the theme
folder). Free for personal and commercial use. The illustrations and portraits are included under
the same license. The Inter font is licensed under the SIL Open Font License 1.1
(assets/fonts/inter/OFL.txt); the icons are based on Heroicons (MIT).