Overview#
Brief is a website for a solo lawyer or independent solicitor, built as a WordPress block theme. The
fictional brand is Pennington Employment Law, the Southampton practice of employment solicitor Tessa
Pennington, who advises employees and senior staff on settlement agreements, redundancy, dismissal,
discrimination and problems at work, mostly for fixed fees. The theme has a home page with the
plain-English promise, credentials, four areas of help, the four-step process, a fixed-fee teaser,
client reviews and an FAQ; services filterable by area with what's included, timelines and fees; a
fees page with a fee estimator (VAT, employer contributions, payment plans), the full fee table and
staged tribunal fees; a branching "what are my options?" check with a tribunal deadline calculator;
searchable guides and a long-form article with a reading progress bar and a table of contents; an
about page with credentials, the office and regulation and complaints information; a four-step
booking flow with conflict-check fields and a calendar file; and a 404 page. 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/brief.
Headings, copy, cards, lists, buttons and images are core WordPress blocks arranged in block
patterns, so you edit them in the block editor without touching code. The interactive parts (the
filters, the fee estimator, the options check, the deadline calculator, the booking flow and the
message form) work as in the HTML version: they are small Custom HTML blocks driven by
assets/js/main.js. Colours and fonts are design tokens in theme.json: change them in
Appearance > Editor > Styles and the whole site follows.
The sample copy is general information about employment law in England and Wales, written for a fictional practice. Before you publish, have a qualified lawyer in your jurisdiction review every legal statement, fee, time limit and regulatory detail, and replace the placeholders (registration number, insurance, complaints procedure) with your own.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
brief.zipfrom freeframe.dev/templates/brief (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 Brief.
- Go to Appearance > Brief setup and click Create the demo pages. This creates the Home, Services, Fees, Check your options, Guides, the sample article, About and Book a call 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 > Brief pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Brief.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (portrait and fixed-fee cards), credentials, areas, how-it-works, fees-teaser, reviews, faq, cta |
| Services | /services/ |
page-services.php |
services-header, services-list (area filter and eight services), cta |
| Fees | /fees/ |
page-fees.php |
fees-header, estimator, fee-table, tribunal-stages (stages, contributions, payment plans, insurance), cta |
| Check your options | /check/ |
page-check.php |
check-header, options-check, deadline-calculator, cta |
| Guides | /resources/ |
page-resources.php |
guides-header, guides-list (featured guide, topic chips, search, nine guides), cta |
| Settlement agreements explained | /article/ |
page-article.php |
article (header, contents, text, author box and the side card), keep-reading, cta |
| About | /about/ |
page-about.php |
about-intro, promises, background (timeline), office, regulation, cta |
| Book a call | /book/ |
page-book.php |
book-header, booking, message |
| 404 | any missing URL | templates/404.html |
404 |
The cta card ("Not sure where you stand? Let's talk it through.") is one pattern shared by every
page except the booking page: insert it anywhere from Patterns > Brief. Posts use
templates/single.html; the blog, archives and search results use templates/index.html,
templates/archive.html and templates/search.html. The article page is an ordinary page: add more
guides as pages (duplicate it from Pages) or as posts.
Project structure#
freeframe-brief/
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 (with its strings and page links), editor styles,
the `js` class script, theme colour and favicon, 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 block styles
inc/site-setup.php Appearance > Brief 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 filters, estimator, options check, deadline calculator, booking, forms...
assets/icons/ SVG icons for the Icon block
assets/img/ portrait, office illustration, logo, favicon, social image
assets/fonts/ Literata and Albert Sans, with their licenses
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-brief.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:
| Colour | Default | Used for |
|---|---|---|
| Paper | #f5f3ec |
the page background |
| Paper 2 | #ece9df |
sunken panels, tags |
| Card | #fffdf8 |
cards and inputs |
| Line, Line strong | #dcd8ca, #bfb9a6 |
hairlines and input borders |
| Ink | #13221c |
text, primary buttons, dark sections |
| Ink 2, Ink 3 | #1c3229, #2c473b |
raised surfaces and hovers on ink |
| Slate | #4c5852 |
secondary text (AA on Paper and Card) |
| Slate soft | #b6c4bb |
secondary text on ink |
| Pine, Pine soft | #1f5a43, #dfe9e2 |
links, icons and icon tiles |
| Lime, Lime soft, Lime deep | #d4eb5c, #edf6bd, #b8d13a |
the highlighter accent: marks, chips, buttons on ink |
| Danger, Danger soft, Amber soft, Amber ink | #b42318, #fbe9e6, #fbefd2, #7a4b06 |
form errors and time-sensitive notices |
| White, Black | for your own blocks |
The theme's stylesheet reads these colours from the palette (bg-ink, text-pine,
border-line...), so changing Lime in Styles recolours every highlighter mark, chip and lime
button at once. Lime is a fill colour: don't use it for text on light backgrounds. The SVG artwork
(the logo, the portrait, the office, the document drawings and the decorations drawn in
src/theme.css) carries its own colours; search for #d4eb5c and #13221c to update them too.
The page headers' ruled-paper background is the class ff-ruled on the section (with
ff-ruled-80 for a shorter fade), the dark sections use on-dark ink-glow, and the faint document
in the call-to-action card is ff-cta-art: add these to a Group's Additional CSS class(es).
Typography#
Two fonts are bundled in assets/fonts/ and declared in theme.json
(settings.typography.fontFamilies), so no request goes to Google: Literata (300 to 600 and
italic, with optical sizes; preset serif) for headings, prices and quotes, and Albert Sans
(400 to 700 and italic; preset sans) for everything else. To use other fonts, install them in
Styles > Typography > Fonts (the Font Library) and select them, or replace the fontFace
entries in theme.json. The utility classes read the presets (--wp--preset--font-family--serif
and --sans), so keep those slugs.
Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). The
classes display (Literata at large optical sizes), eyebrow (small caps label with a lime bar),
lede (large secondary text), clause (small italic note) and mark (the highlighter swipe, on a
span inside a heading) give text the template's styles.
Content#
Edit pages in Pages: click any text to change it, select a button to change its link. 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 > Brief.
What the script drives is in Custom HTML blocks: edit those in the block's HTML view, and keep
their ids and data- attributes.
| Pattern | Custom HTML | What it is |
|---|---|---|
services-list |
the area chips, the "8 services shown" status, the empty state | see Filters and search |
estimator |
the estimator form and the result panel | see Fee estimator |
fee-table |
the fee table | a scrollable table; keep it in step with MATTERS |
options-check |
the intro card, the questions and the result | see Options check |
deadline-calculator |
the calculator form and its result | see Deadline calculator |
guides-list |
the featured guide's drawing, the topic chips and search, the status, the empty state | see Filters and search |
article |
the reading progress bar | see Article reading aids |
reviews |
the star rating line | decorative stars and the review count |
booking, message |
the booking flow and the message form | see Booking flow and Forms |
404 |
the struck-out page drawing | an SVG illustration |
header |
the menu button | driven by the script |
The data behind the interactive parts lives at the top of the second part of
assets/js/main.js, as in the HTML version:
| Constant | What it holds | Used by |
|---|---|---|
MATTERS |
each matter's name, area, timeline, whether an employer contribution applies, three levels ([min, max] in pounds, excluding VAT) and three hints |
fee estimator, options check results, booking topics |
LEVELS |
['straightforward', 'standard', 'complex'], the ?level= values |
fee estimator |
VAT_RATE, PLAN |
0.2; { minFee: 1000, months: 3 } |
fee estimator |
CHECK_STEPS, CHECK_RESULTS |
the questions and the 15 result screens | options check |
DEADLINE_RULES |
claim types and their time limits in months | deadline calculator |
CALL_TYPES, AVAILABILITY |
call types (label, minutes, price, where); time zone, weekly hours, notice, closed days | booking flow |
Their visible strings are wrapped in __() and translated in inc/setup.php (see
Translations). To add a matter, add an entry to MATTERS (in a child theme, see
Child themes):
holiday: { name: __('Holiday pay claim'), area: __('Claims and disputes'), timeline: __('1–2 weeks'), contribution: false, levels: [[300, 300], [450, 450], [600, 900]],
hints: [__('Checking your figures'), __('Plus a letter to your employer'), __('Several years of underpayment')] },It then appears in the estimator's matter list and the booking topic list. Also add a service card and a fee table row, so the pages match the estimator.
Some blocks carry classes (Block settings > Advanced > Additional CSS class(es)) that the script
reads. Blocks can't store data- or ARIA attributes, so the script turns classes starting with
ffd- and ffa- into them when the page loads: ffd-tags--leaving on a service card becomes
data-tags="leaving", and ffa-aria-hidden--true becomes aria-hidden="true" (an underscore in
the value is a space: ffd-tags--dismissal_discrimination).
revealfades a block in as it scrolls into view (reveal-delay-1to-3stagger it).link-cardon a group makes the whole group clickable: the first link inside it (in the heading) stretches over the group.card,card-hover,notice,tag,tag-lime,icon-tileare the template's card, notice, label and icon box styles.
Adding a service
On the Services page, select a service (a List item group with an anchor such as settlement
and the class ffd-tags--leaving) in List View and duplicate it. Give the copy its own HTML
anchor (Block settings > Advanced; links such as /services/#settlement use it, and so does the
estimator's "What's included" link, which uses the MATTERS key) and an area class: one of
ffd-tags--leaving, ffd-tags--at-work, ffd-tags--disputes or ffd-tags--new-job. Change the
title, text, what's included, timeline, fee and the two buttons' links
(/book/?topic=<key>, /fees/?matter=<key>#estimator). The chip counts and the "8 services shown"
status update themselves.
Adding a guide
On the Guides page, duplicate a guide card (a List item group with ffd-guide, a topic class such
as ffd-tags--redundancy and optional search words such as ffd-text--calculation_weekly_pay).
Topic classes must match the chips' data-tag values in the filter bar's Custom HTML (settlement,
dismissal, redundancy, discrimination, at-work, contracts); a card can have several, joined
by _. Search matches the card's text plus its ffd-text-- words.
Images and artwork#
The portrait (assets/img/tessa-portrait.svg, 480 × 560) on the home, about, article and booking
pages and the office illustration (assets/img/office.svg, 640 × 460) on the about page are SVG
files in Image blocks: select one and Replace it with a photo, keeping a 6:7 portrait crop for
Tessa (the small crops use object-cover object-top). The logo mark is assets/img/logo.svg in the
header and assets/img/logo-light.svg in the footer. Icons are Icon blocks: select one and pick
another from the Brief collection or WordPress's own. The social image is
assets/img/og-image.png (1200 × 630, source og-image.svg); the favicon assets/img/favicon.svg
is used until you set a Site Icon in Settings > General.
The featured guide's document, the options check's questionnaire and the 404 page's struck-out page are inline SVG in Custom HTML blocks; replace any of them with an Image block if you prefer.
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": Home, Services, Fees, Your options, Guides, About, Book a free call). Its first link
(Home) and last link (Book a free call) are hidden in the desktop header, which has the logo and
its own booking button; keep them first and last, or remove the .site-nav … :first-child and
:last-child rules in src/theme.css. 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 article marks Guides as the current page (the class
ff-section-resources on the article section).
The footer's Help with and Practice menus are List blocks: click a link to change it, press Enter to add one. The footer also holds the not-legal-advice notice and the regulatory statement: keep both, and replace the placeholder registration number with your own. The phone number and email appear in the header, the footer, the check and booking pages; search for them when you change them.
Features#
Fee estimator#
On the Fees page (#estimator), in the estimator pattern's Custom HTML ([data-estimator]).
Code: initEstimator() in main.js.
- The matter list is built from
MATTERS. The three level cards (input[name="est-level"], values0to2) show the matter'shints. - When
minequalsmaxthe result shows a fixed fee with a "Fixed fee" tag; otherwise a range with a "Range: fixed in writing before work starts" tag. - Show prices including VAT multiplies by
1 + VAT_RATE. The breakdown always lists the fee, VAT and the total including VAT. - My employer is contributing only appears for matters with
contribution: true(settlement agreements and exit negotiations). The amount (default 500, excluding VAT) is deducted from the fee, never below zero. - I'd like to spread the cost shows
PLAN.monthsequal payments when the amount you pay (after any contribution, excluding VAT) is at leastPLAN.minFee, and otherwise explains why it isn't available. - The matter and level are written to the URL (
/fees/?matter=tribunal&level=complex), so links such as "Estimate this fee" on the services page open the right estimate. The "Book a free call about this" and "What's included" buttons follow the matter.
The result is announced politely to screen readers (aria-live). Every control is a native select,
radio, checkbox or number input, so it works with a keyboard.
Options check#
On the Check your options page, in the options-check pattern's Custom HTML ([data-check]).
Code: initCheck(), replayCheck() and readCheck().
Each step in CHECK_STEPS has a question q, an optional hint and options. An option has
either next (another step id) or result (a key of CHECK_RESULTS). The first step is always
start. A result has a tag, title, summary, steps, an optional urgent message, a
service (a MATTERS key, or null) and a deadline (a DEADLINE_RULES key, or null).
The result screen shows the summary and next steps, an amber "Time-sensitive" box when urgent is
set, the recommended service with its lowest fee and links to the services page, estimator and
booking (/book/?topic=<service>), a "There may be a time limit" prompt that preselects the right
claim type in the calculator, and a collapsible recap of the answers. Back and Start again buttons
let visitors change their answers.
To add a branch, add a step and point an option at it:
'tupe-stage': { q: __('Has the transfer happened yet?'), hint: '', options: [
{ label: __('Not yet'), result: 'general' },
{ label: __('Yes'), result: 'general' }] },Each question is a <fieldset> with a <legend> that receives focus when it appears; answers are
radio buttons styled as cards. A missing answer shows an inline error with role="alert".
Deadline calculator#
On the Check your options page (#deadline), in the deadline-calculator pattern's Custom HTML
([data-deadline]). Code: deadlineFrom() and the block after it.
It applies the "three months less one day" rule: the last day is the day before the same date three
months later (15 March gives 14 June). If the later month has no such date (31 August to November),
the last day is the final day of that month. DEADLINE_RULES sets the months per claim type;
statutory redundancy pay uses six. The result shows the date, the number of days left, and warnings
when the date is 21 days away or less, today, or has passed. Dates must be within the last two years
or the next six months.
This is an estimate only and says so on screen. It doesn't model early conciliation extensions, continuing acts or late-claim discretion, and it doesn't store anything.
Booking flow#
On the Book a call page, in the booking pattern's Custom HTML ([data-booking]). Code:
initBooking().
- Call type from
CALL_TYPES./book/?type=advicepreselects one. - Date and time: a month calendar (Monday first) and time slots generated from
AVAILABILITY: weeklyhourswindows in UK time,slotStepminutes apart, at leastminNoticeHoursfrom now, up todaysAheaddays ahead, skippingcloseddates.demoBusySharehides a stable share of slots so the demo looks realistic; set it to0when you connect a real calendar. - Details: name, email, optional phone, topic (
/book/?topic=settlementpreselects it), the conflict-check fields (employer, required; anyone else involved, optional), an optional deadline, a short description and a required acknowledgement that booking doesn't create a client relationship. - Confirmation with a summary and a Save a calendar hold (.ics) button.
Times are always generated in Europe/London, so they follow UK daylight saving. If the visitor's
device is in another time zone, each slot also shows their local time, and so do the summary and
confirmation.
The calendar is a table of buttons with a roving tabindex: arrow keys move by day and week, Home
and End to the start and end of the week, Page Up and Page Down by four weeks. Unavailable days are
aria-disabled, the chosen day is aria-pressed, and each day has a full label such as "Tuesday
20 October 2026, 9 times available". Each step's heading receives focus when the step opens.
Filters and search#
- Services (
ffd-filter-rooton the services list): the chips in the Custom HTML (data-filter) show cards whoseffd-tags--class contains the value, update the counts and the "8 services shown" status, and keep the choice in?area=(the home page's area cards and the footer link to/services/?area=leavingand so on). - Guides (
ffd-guideson the guides list): topic chips (data-tag) and the search box combine. Search matches every word against the card text and itsffd-text--words. Both are kept in the URL as?tag=and?q=. When nothing matches, an empty state offers to clear everything.
Chips are buttons with aria-pressed, and the result counts are in a polite live region.
Article reading aids#
On the article page, the reading progress bar ([data-progress], a Custom HTML block at the top of
the article section) fills as you scroll through the article (ffd-article). The table of
contents (ffd-toc, a List of links) marks the section you're reading with aria-current="true".
Each Heading 2 in the article needs an HTML anchor that matches a contents link (#what,
#advice...).
FAQ accordion, scroll reveals and motion#
The FAQ on the home page is an Accordion block (WordPress's own, with its keyboard support and
ARIA): add a question by duplicating an item in List View. The square plus/minus marker is drawn by
CSS (.wp-block-accordion-heading__toggle::after in src/theme.css). Blocks with reveal fade in
as they scroll into view, and the hero highlighter (draw-mark) draws in once. With "reduce
motion" turned on, everything appears immediately.
Header, mobile menu and back to top#
The header gains a border once you scroll. Below 1024 px the menu button opens the mobile menu,
updating aria-expanded and its label; Escape closes it and returns focus to the button. The
back-to-top button appears after about one screen of scrolling and hides while the footer's "Back to
top" link is visible. To remove it, delete the "Back to top" part at the end of assets/js/main.js
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 ink button), Outline, Lime, Outline (on dark) |
The classes is-large and is-small on a Button block make it the hero or compact size. Calendar
and arrow icons on buttons are classes too (icon-before-calendar, icon-after-arrow-right, with
icon-size-20); see src/icons.css for the available icons.
Forms#
There are three forms. None of them sends anything until you connect it; their confirmation messages say so.
| Form | Where | Validates |
|---|---|---|
Booking details ([data-booking-form]) |
Book a call, step 3 | name, email, optional phone (10+ digits), topic, employer, description (20+ characters), acknowledgement |
Message ([data-contact-form]) |
Book a call | name, email, message (20+ characters) |
Deadline calculator ([data-deadline]) |
Check your options | a date within the last two years or the next six months |
Validation runs on submit and again as fields are corrected. Each field declares its rule with
data-validate (required, email, phone-optional, message, consent, date-event) in the
forms' Custom HTML. Errors appear under the field, are linked with aria-describedby, set
aria-invalid="true", and focus moves to the first invalid field.
To collect messages, use a form plugin (replace the message form's Custom HTML block with the
plugin's block; add the classes label, input and btn btn-primary to its labels, fields and
button), or post the form where the script marks it ("Connect a real endpoint here") in
assets/js/main.js (in a child theme), for example to Formspree:
fetch('https://formspree.io/f/your-id', {
method: 'POST',
body: new FormData(cf),
headers: { Accept: 'application/json' }
}).then(function (res) {
if (!res.ok) throw new Error('Send failed');
// show the success panel here
}).catch(function () {
alert('Sorry, your message could not be sent. Please email or call instead.');
});Move the lines that hide the form and show [data-success] into the then callback, and change
the success text from "ready to send" to "sent" (Thanks, %s. Your message is ready to send. in
main.js and its translation in inc/setup.php).
Taking real bookings#
The simplest route is a scheduling tool such as Cal.com or Calendly: replace step 2 of the booking
Custom HTML with the tool's embed, or send people straight to your booking link from step 1. If you
keep this calendar, post the details form to your form service (as above, using
new FormData(form)) and add the chosen slot:
var data = new FormData(form);
data.append('call_type', state.type);
data.append('start', state.slot.at.toISOString());Set AVAILABILITY.demoBusyShare to 0, and keep closed up to date with your holidays. Because
enquiries can contain sensitive personal information, use a form service that stores data in a
suitable region, and say in your privacy notice how you handle it.
Saved data (localStorage)#
| Key | Shape | Written by |
|---|---|---|
brief-check-v1 |
{ "answers": [0, 1], "date": "2026-10-15" } |
options check, when a result is reached |
This is the HTML version's key. answers holds the chosen option index for each question in order.
On load, readCheck() replays the answers through CHECK_STEPS; if any index is out of range, or
the answers don't end in a result, the saved value is ignored and the intro is shown. If you change
the questions, bump CHECK_KEY (for example to brief-check-v2) so old answers are dropped.
Start again removes the key; visitors can also clear site data in their browser. Nothing else is
stored, and nothing is sent anywhere.
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Services — Pennington Employment Law"); 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. A practice benefits from LegalService structured data with your real name,
address, phone and opening hours; most SEO plugins can add it. Check your regulator's rules on
advertising, reviews and how fees must be published.
Accessibility#
Built in: a skip link to the main content (added by WordPress, styled by the theme), landmarks
(header, labelled navigation, main, footer), one h1 per page, labelled sections, visible focus
rings in pine (lime on dark sections), the menu button with aria-expanded and a label that changes
with its state, filter chips with aria-pressed and live result counts, the Accordion block for the
FAQ, a live region for the estimate, question fieldsets whose legend receives focus, a keyboard
calendar with full day labels, form errors announced through aria-describedby and aria-invalid,
focus moved to new steps and results, and reduced-motion support for every animation. Body text
(Slate on Paper) is about 7:1. The demo passes axe (WCAG 2.1 A and AA).
When you edit: keep one Heading 1 per page, give images alt text (or mark them decorative), keep
link text meaningful, label any field you add to a form (<label for="...">), and keep text on Lime
dark (Ink).
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, template or the script's markup,
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-brief, 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-brief. All text in patterns,
templates and PHP goes through WordPress's translation functions; the script's strings (matters and
fees, the options check, deadline rules, booking lines, result counts, form errors and messages) are
translated in PHP (freeframe_brief_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-brief wp-content/themes/freeframe-brief/languages/freeframe-brief.potSave freeframe-brief-cy.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Brief setup is copied into your pages in the site's
language at that moment; translate it in the editor like any other content. Prices are formatted in
pounds and dates in British English by the script (money(), longDate()).
The script writes links to the theme's pages (the estimator's buttons, the options check's service
links). They come from freeframe_brief_page_urls() in inc/setup.php; change it if your pages
have other slugs.
Child themes#
Use a child theme for changes you want to keep when Brief is updated (your fees, questions and
availability in particular). Create wp-content/themes/brief-child/style.css:
/*
Theme Name: Brief Child
Template: freeframe-brief
*/Templates, parts, patterns and theme.json in the child theme override the parent's files of the
same name. To use your own copy of assets/js/main.js, dequeue the parent's script
(freeframe-brief) and enqueue yours in the child's functions.php, keeping the inline
window.freeframeBrief strings and page links (or copy them). The parent's stylesheet only contains
the classes the parent uses: if your child theme uses 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 > Brief.
- The estimator, check or booking shows nothing. Check the browser console. A typo in
MATTERS,CHECK_STEPSorCALL_TYPES(a missing comma or quote) stops all ofmain.js. - An option in the check leads nowhere. Every
nextmust name a key ofCHECK_STEPS, and everyresulta key ofCHECK_RESULTS. - The check keeps showing an old result. It's saved in
localStorage. Click Start again, or bumpCHECK_KEYafter changing the questions. - A new service doesn't show up under an area chip. Its List item group needs one of the
ffd-tags--area classes. See Adding a service. - Home or Book a free call is missing from the desktop menu. The desktop header hides the menu's first and last links. See Navigation, header and footer.
- Brief 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 > Brief pages.
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 portrait, office and document illustrations and
the logo are included under the same license. Literata and Albert Sans are licensed under the SIL
Open Font License 1.1 (OFL.txt in each folder of assets/fonts/).