Freeframe

Documentation

Brief

v1.0.0Updated Oct 15, 2026A calm, plain-English website for a solo lawyer or solicitor, with fixed fees, an options checker and booking.

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.

  1. Download brief.zip from freeframe.dev/templates/brief (the WordPress tab).
  2. 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.
  3. 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.
  4. 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:

sh
pnpm install
pnpm dev     # builds the CSS, then starts WordPress with the theme at http://127.0.0.1:9400

pnpm 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.txt

Customising#

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):

js
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).

  • reveal fades a block in as it scrolls into view (reveal-delay-1 to -3 stagger it).
  • link-card on 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-tile are 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.

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"], values 0 to 2) show the matter's hints.
  • When min equals max the 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.months equal payments when the amount you pay (after any contribution, excluding VAT) is at least PLAN.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:

js
'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().

  1. Call type from CALL_TYPES. /book/?type=advice preselects one.
  2. Date and time: a month calendar (Monday first) and time slots generated from AVAILABILITY: weekly hours windows in UK time, slotStep minutes apart, at least minNoticeHours from now, up to daysAhead days ahead, skipping closed dates. demoBusyShare hides a stable share of slots so the demo looks realistic; set it to 0 when you connect a real calendar.
  3. Details: name, email, optional phone, topic (/book/?topic=settlement preselects 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.
  4. 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.

  • Services (ffd-filter-root on the services list): the chips in the Custom HTML (data-filter) show cards whose ffd-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=leaving and so on).
  • Guides (ffd-guides on the guides list): topic chips (data-tag) and the search box combine. Search matches every word against the card text and its ffd-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:

js
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:

js
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/:

sh
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:

sh
wp i18n make-pot wp-content/themes/freeframe-brief wp-content/themes/freeframe-brief/languages/freeframe-brief.pot

Save 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:

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 build after 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_STEPS or CALL_TYPES (a missing comma or quote) stops all of main.js.
  • An option in the check leads nowhere. Every next must name a key of CHECK_STEPS, and every result a key of CHECK_RESULTS.
  • The check keeps showing an old result. It's saved in localStorage. Click Start again, or bump CHECK_KEY after 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/).

Search documentation

Search the guides and every template's documentation