Overview#
Fade is a website for a barbershop, built as a WordPress block theme. The demo brand is Fade Bros.
Barber Co., a fictional independent barbershop with two shops in Chicago: Logan Square (2741 N
Milwaukee Ave) and Pilsen (1532 W 18th St). The theme has a home page with an illustrative
"current wait" board for each shop, a services preview, the barbers, the house rules, reviews, both
shops with live open-now badges and a gift card call to action; a services page with a price list,
a category filter and an FAQ; a barbers page with shop and speciality filters and each barber's
next free time; a five-step booking flow that works out time slots from each barber's schedule and
the length of the chosen services; a shop with a saved bag and a gift card builder; locations with a
map, hours, transit, parking and the walk-in wait; an about page with the story, a timeline, the
standards and careers; a contact page; 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/fade.
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
wait board, the filter chips, the booking flow, the bag, the gift card builder and the forms) 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.
Quick start#
Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.
- Download
fade.zipfrom freeframe.dev/templates/fade (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 Fade.
- Go to Appearance > Fade setup and click Create the demo pages. This creates the Home, Services & prices, Barbers, Book a chair, Shop & gift cards, Locations & hours, About & careers and Contact 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 > Fade pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Fade.
| Page | Slug | Page pattern | Sections (pattern files in patterns/) |
|---|---|---|---|
| Home | / |
page-home.php |
home-hero (wait board and stamp), marquee, menu-teaser (four service tickets), team-teaser (four barbers), house-rules, reviews, shops (both shops with open-now status), gift-teaser |
| Services & prices | /services/ |
page-services.php |
services-hero, service-menu (category filter and the price list), services-faq, services-cta |
| Barbers | /barbers/ |
page-barbers.php |
barbers-hero, barber-list (filters and six profiles), barbers-cta |
| Book a chair | /book/ |
page-book.php |
book-hero, booking (the five steps and the summary) |
| Shop & gift cards | /shop/ |
page-shop.php |
shop-hero, products (filter, eight products and the bag), gift-cards (builder and live preview) |
| Locations & hours | /locations/ |
page-locations.php |
locations-hero, map, location-logan, location-pilsen (address, transit, parking, hours, walk-in wait), wait-model |
| About & careers | /about/ |
page-about.php |
about-hero, story (storefront illustration), timeline, standards, careers |
| Contact | /contact/ |
page-contact.php |
contact-hero, contact-form (form, both shops, group bookings) |
| 404 | any missing URL | templates/404.html |
404 |
Every page shares the header (with the bag count) and the footer (open-now badges for both shops
and the newsletter form). Posts use templates/single.html; the blog, archives and search results
use templates/index.html, templates/archive.html and templates/search.html. For a plain page
of text, choose the Page with title template.
Project structure#
freeframe-fade/
style.css theme header (name, version, requirements, license); no styles
theme.json design tokens: palette, fonts, spacing, layout widths, button
functions.php loads the files in inc/
inc/setup.php stylesheet and script (with its strings), 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 > Fade 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 FADE data, open-now, wait estimate, filters, booking, bag, gift cards, forms
assets/icons/ SVG icons for the Icon block
assets/img/barbers/ barber portraits, one SVG per barber id (400 x 480)
assets/img/ logo, favicon, social image
assets/fonts/ Anton and Archivo, 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-fade.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 | #f2ead9 |
the page background |
| Card | #fbf7ef |
cards and inputs |
| Sand | #e8dcc4 |
soft panels and alternate sections |
| Line | #cdbd9f |
hairlines on paper |
| Ink | #151515 |
text, borders, hard shadows, dark sections |
| Coal | #24211e |
raised surfaces on dark (the wait board) |
| Mute | #5a5249 |
secondary text |
| Barber red, Barber red dark | #b5281c, #8f1c12 |
buttons, eyebrows, links, the button hover |
| Barber blue | #1d3557 |
the blue in the barber-pole stripes |
| Brass | #e0a83a |
accents on dark backgrounds only |
| Go green | #2c6a37 |
"Open" dots and positive states |
| Error | #b3261e |
form errors |
| White, Black | for your own blocks |
The theme's stylesheet reads these colours from the palette (bg-red, text-mute,
border-brass...), so changing Barber red in Styles recolours every red button, eyebrow and link at
once. Keep text colours at 4.5:1 contrast or more against Paper, Card and Sand; Brass is only for
text on Ink. The barber-pole stripes are the pole-stripe utility in src/theme.css (a
repeating-linear-gradient of red, paper and navy); the header and footer use it through the
pole-band group, and the home hero and the 404 through ff-pole. The SVG artwork (portraits,
products, the map, the storefront, the stamp, the logo and the social image) has its colours
written in: search for the old values (for example #b5281c) to change them too.
Typography#
Two fonts are bundled in assets/fonts/ and declared in theme.json
(settings.typography.fontFamilies), so no request goes to Google: Anton (preset display)
for headings, set in uppercase, and Archivo (preset sans), a variable font with weights 400 to
900 and a width axis from 62% to 125%. The condensed class and the button, tag and label styles
use font-stretch for the narrow, sign-painter look. 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--display and
--sans), so keep those slugs. The SVGs with text name the fonts directly
(font-family="Anton, Impact, sans-serif").
The classes font-display (Anton, uppercase), condensed (narrow Archivo), eyebrow (the small red
label with a rule), section-title (the big section heading) and lede (the larger intro paragraph)
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 > Fade.
What the script drives, and pictures drawn in SVG with text, are Custom HTML blocks: edit those
in the block's HTML view, and keep their ids and data- attributes.
| Pattern | Custom HTML | What it is |
|---|---|---|
home-hero |
the stamp, the wait board | see Current wait estimate |
marquee, gift-teaser, 404 |
the scrolling services, the gift card stack, the razor | decorative pictures and text |
service-menu |
the category chips and the count | see Services filter |
barber-list |
the shop and speciality chips, the count, the empty message | see Barber filters |
booking |
the five steps and the summary | see Booking flow |
products |
the product chips, the count, the product pictures, the "Add to bag" buttons, the bag | see Bag and checkout |
gift-cards |
the builder form, the live preview and its announcement | see Gift card builder |
map, story |
the map of both shops, the storefront | SVG pictures |
location-logan, location-pilsen |
the hours table, the walk-in wait card | see Open now and hours |
contact-form |
the form and its success message | see Forms |
header, footer |
the bag link, the menu button, the newsletter form | driven by the script |
Some blocks carry classes (Block settings > Advanced > Additional CSS class(es)) that the script
reads. Blocks can't store data- attributes or hidden, so the script turns classes starting with
ffd- into attributes when the page loads, and ff-hidden into hidden: ffd-open-now--logan on
a badge becomes data-open-now="logan", and ffd-locs--logan_pilsen on a barber card becomes
data-locs="logan pilsen".
revealfades a block in as it scrolls into view.card,card-shadow,ticket,tag,tag-solid,status,link-arrowandtext-linkare the template's card, ticket, tag, badge and link looks.
The data the scripts need lives in one object, FADE, at the top of assets/js/main.js:
| Key | What it holds |
|---|---|
shop |
Name, email, timeZone (America/Chicago), currency and locale |
locations |
The two shops: id, address, phone, number of chairs, hours per weekday (0 = Sunday, null = closed), transit and parking |
categories |
Service categories (cuts, beard, shaves, kids, packages) |
services |
Each service: id, cat, name, mins, price, optional needs and popular |
specialties |
The speciality filter on the barbers page |
barbers |
Each barber: id, short name, role, skills, specialties and schedule per shop per weekday |
traffic |
The demo walk-in traffic curve used by the wait estimate |
products, productCategories |
Shop products and their filter categories |
giftDesigns, gift, shipping |
Gift card designs and limits, flat shipping and the free-shipping threshold |
The visible lists (the price list, the barber cards, the product cards, the hours tables) are page
content, so they are indexed by search engines and you edit them in the editor. When you change a
service, barber or product, change it in both the page and FADE (in a child theme, see
Child themes). Visible names in FADE are wrapped in __() so they can be
translated.
Adding a service
Add an entry to FADE.services:
{ "id": "towel-lineup", "cat": "cuts", "name": __("Hot towel & line-up"), "mins": 20, "price": 24, "desc": "A line-up finished with two hot towels." }Then, on the Services & prices page, duplicate a row of the Cuts group (a list item group with the
class svc-row) in List View and change its name, description, time, price and the button link
(/book/?service=towel-lineup). In the booking pattern's Custom HTML, copy a <li> of the Cuts
list in step 2 and set value="towel-lineup". needs lists the skills a barber must have; it
defaults to the service's own category. Packages use it, for example needs: ['cuts', 'shaves'] for
The Full Service, so only barbers with both skills are offered.
Adding a barber
Add an entry to FADE.barbers, a portrait at assets/img/barbers/<id>.svg (the booking flow loads
it by id), and a card on the Barbers page: duplicate a card (a list item group with the classes
reveal ffd-barber--marcus ffd-locs--logan ffd-specs--fades_beards) and change its classes, picture,
text, "Book with" link (/book/?barber=<id>) and the empty group with ffd-next-free--<id>, which
the script fills with the next free time.
{
"id": "nia", "short": __("Nia"), "name": __("Nia Brooks"), "role": __("Barber"), "years": 4,
"skills": ["cuts", "beard", "kids"], "specialties": ["fades", "kids"],
"bio": "…", "signature": "Burst fade",
"schedule": { "logan": { "4": ["12:00", "20:00"], "5": ["12:00", "20:00"] } }
}Keep barber schedules inside the shop's opening hours.
Images and artwork#
Everything is SVG, so there are no photos to license:
- Barber portraits:
assets/img/barbers/<barber id>.svg, 400 x 480 (5:6), in Image blocks on the home and barbers pages: select one and Replace it with a photo of the same proportions. The booking flow shows every SVG in that folder by barber id (freeframe_fade_barber_images()ininc/setup.phppasses them to the script aswindow.freeframeFade.barbers): keep the file names, or change the*.svgpattern there if you use JPGs. - Products: drawn inline in each product card (
.product-art, square) in Custom HTML blocks; replace the<svg>with an<img>of a square product shot. - Storefront (About) and map (Locations) are inline SVGs in Custom HTML blocks so they use the theme's fonts. Replace either with an Image block if you prefer a picture.
- Icons are Icon blocks: select one and pick another from the Fade collection or WordPress's own.
- Logo:
assets/img/logo.svg(header) andassets/img/logo-light.svg(footer), in Image blocks next to the Fade Bros. name. - Social image:
assets/img/og-image.png(1200 x 630, sourceog-image.svg). Favicon:assets/img/favicon.svg, 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": Services, Barbers, Shop, Locations, About, 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. WordPress marks the current page in the menu. The bag link (with its count) and the menu button are Custom HTML blocks; the "Book a chair" buttons are Button blocks.
The footer's links are List blocks: click a link to change it, press Enter to add one. The shops' addresses, phone numbers and open-now badges also appear on the home, locations and contact pages; search for them when you change them.
Features#
Booking flow#
The Book a chair page, section 9 of assets/js/main.js. Five steps inside one
<form id="booking-form"> in the booking pattern's Custom HTML:
- Shop: radio cards with open-now status and who is in today.
- Services: checkboxes; the duration and total add up in the summary and the running total.
- Barber: "First available" plus every barber who works at the chosen shop and has the skills for every chosen service, with their next free time. Barbers left out are listed with the reason (for example "Rosa (no straight-razor shaves)").
- Time: a month calendar and the open times for the chosen day, grouped into morning, afternoon and evening.
- Details: name, mobile, email, notes, reminder and cancellation policy, all validated.
If nobody at the chosen shop can do every chosen service in one visit (a kids cut and a hot towel
shave in Pilsen), step 2 explains it instead of continuing. The confirmation shows a summary, a
reference and a button that downloads an .ics file. It clearly says nothing was booked.
URL parameters preselect choices: /book/?location=pilsen, ?service=fade, ?barber=rosa (a
barber also sets their shop). Only ids that exist in FADE are accepted. The service tickets,
price list, barber cards and shop cards link here with these parameters.
Accessibility: each step's heading receives focus when the step changes, and the step list marks
the current step with aria-current="step". The calendar uses a roving tabindex: the arrow keys
move by day and week, Home and End go to the start and end of the week, and Page Up and Page Down
move four weeks. Each day's label says how many times are open; errors use role="alert".
Time slots#
Slots come from slotsFor(locId, key, ids, barberId, now):
- The shop's hours that day, the barber's
scheduleat that shop, and the total minutes of the chosen services. - Start times every
SLOT_STEP(15) minutes; today's earliest isLEAD_TIME(30) minutes from now. - Visitors can book
BOOK_AHEAD(42) days ahead. isBooked()marks about a third of each barber's half-hours as taken, using a stable hash so the diary looks real and doesn't change on reload. Replace it with your booking system's availability.
var SLOT_STEP = 15; // minutes between start times
var LEAD_TIME = 30; // earliest booking is this many minutes from now
var BOOK_AHEAD = 42; // days visitors can book ahead (6 weeks)All times are the shop's local time (FADE.shop.timeZone), whatever the visitor's own time zone is.
Connecting real bookings#
The flow is a front end only. To take real bookings, either replace the booking section's Custom
HTML with your provider's embed (Square Appointments, Fresha, Booksy and Cal.com all offer one), or
keep this flow and send the request where confirm() says so:
// Demo only: nothing is sent. Post { location, services, barber, date, time, details } to
// your booking provider here (Square Appointments, Fresha, Booksy or Cal.com APIs/embeds).Booking APIs need a secret key, so call them from the server (a small plugin with a REST route, or a serverless function), never directly from the browser.
Current wait estimate#
The home page board ([data-wait-board] in home-hero) and the walk-in wait cards on the Locations
page, section 6 of assets/js/main.js. Any element with data-wait="<shop id>" is filled in:
[data-wait-num], [data-wait-detail], [data-wait-unit], [data-wait-chart] and
[data-wait-chart-label].
estimateWait() multiplies the traffic level for this hour (FADE.traffic, 0 to 1, separate curves
for weekdays, Saturday and Sunday) by 70 minutes, divides by the number of barbers working, adds a
small stable variation and rounds to 5 minutes. It is illustrative: the page says so next to
every number and the wait-model section of the Locations page explains it. To show real waits,
replace estimateWait() with a call to your queue system, and remove the "Illustrative estimate"
labels.
Add ?at=2026-10-17T10:30 to any page URL to preview the site at a shop-local time (useful for
testing busy and closed states).
Open now and hours#
Section 5 of assets/js/main.js. Open-now badges (data-open-now="logan"; in blocks the class
ffd-open-now--logan on a paragraph with the class status) show "Open now · until 8 pm",
"Closing soon · 8 pm" (last 45 minutes) or "Closed · opens tomorrow 9 am". [data-today-hours]
shows today's hours, and the current weekday's row in every [data-hours] table (the hours tables
in the location patterns' Custom HTML) is highlighted and marked aria-current="date". Everything
refreshes every minute. The hours tables are text: when you change FADE.locations[].hours, change
them too.
Services filter#
The Services & prices page, section 7. Chips with data-filter (Custom HTML) show one category;
the groups carry ffd-service-group--cuts and so on, and the count is announced with
aria-live="polite". /services/?category=beard preselects a category.
Barber filters and next free time#
The Barbers page, section 8. Two chip groups filter by shop (data-barber-loc) and speciality
(data-barber-spec); the cards carry their shops and specialities as classes (ffd-locs--,
ffd-specs--). If no barber matches, an empty state offers to reset. ?location=pilsen and
?speciality=curly preselect filters. Each card shows the barber's next free time for a Classic cut.
Bag and checkout#
The Shop page, section 10. "Add to bag" buttons (data-add="<product id>") add products; the bag
lists products with quantity steppers (1 to 10) and gift cards, offers pickup at either shop or
shipping (FADE.shipping: $6, free over $50 in products) with a progress meter, and totals
everything. "Checkout" shows a demo summary. The header bag icon shows the item count on every page
and links to /shop/#bag.
To sell for real, send the bag lines from readBag() to Shopify (Buy Button or Storefront API),
Square Online or Stripe Checkout where the [data-checkout] click handler is, or use a shop plugin
such as WooCommerce instead.
Gift card builder#
The gift-cards section of the Shop page (/shop/#gift-cards), section 11. Preset amounts or a
custom whole amount between FADE.gift.min and FADE.gift.max ($20 to $300), three designs
(gift-pole, gift-stamp, gift-blackout in src/theme.css), recipient, sender, message (140
characters, with a counter) and "right away" or a date within a year. The preview updates as you
type and a screen-reader summary is announced politely. A valid card is added to the bag as a gift
line.
FAQ#
The FAQ on the Services & prices page is made of Details blocks (WordPress's own, native
<details> and <summary>) with the class faq; the first one is open when the page loads (Open
by default in its settings). Add a question by duplicating one in List View. The round plus/minus
icon is drawn by CSS (.wp-block-details.faq > summary::after in src/theme.css).
Header, menu, reveals and back to top#
The header is sticky and gains a shadow after scrolling. Below 1024 px the menu button opens a
full-screen menu (aria-expanded, Esc closes it and returns focus). Blocks with reveal fade up
once as they scroll into view; nothing is hidden with reduced motion or without JavaScript. The
marquee and the stamp stop moving with reduced motion. A back-to-top button appears after about one
screen of scrolling; 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 barber-red button with a hard shadow), Ink, Outline, Paper, Outline (on dark) |
The class is-small on a Button block makes it the compact size. Icons on buttons are classes too
(icon-after-arrow-right); see src/icons.css for the available icons.
Forms#
| Form | Where | Validates |
|---|---|---|
| Booking details | Book a chair, step 5 | Name, 10-digit US mobile, email, policy checkbox |
| Gift card | Shop | Custom amount range, recipient name and email, sender, delivery date |
| Contact | Contact | Name, email, optional phone (10 digits if given), message of 20+ characters |
| Newsletter | footer of every page |
Errors appear inline next to each field, fields get aria-invalid, and focus moves to the first
problem. On success each form shows a message that says it's a demo. No form sends anything until
you connect it. /contact/?topic=careers (or group, chair-rental, feedback, press)
preselects the topic; the careers cards on the About page link there.
To collect messages, use a form plugin (replace the form's Custom HTML block with the plugin's block;
add the classes field-label, field and btn-red to its labels, fields and button), or send the
form in assets/js/main.js (in a child theme). For Formspree, replace the comment in the contact
form handler:
// Send new FormData(contact) to your endpoint here
fetch('https://formspree.io/f/your-id', {
method: 'POST',
body: new FormData(contact),
headers: { Accept: 'application/json' }
}).then(function (res) {
if (!res.ok) throw new Error('Send failed');
contact.reset();
ok.hidden = false;
ok.focus();
});Move the contact.reset() and success lines into the then so the message only shows after a real
send, and update the success text in the contact-form pattern. The newsletter handler has the same
kind of comment; point it at Mailchimp, Buttondown or your email tool.
Saved data (localStorage)#
| Key | Shape | Validation on read |
|---|---|---|
fade-bag |
{ "items": [...], "delivery": "logan" | "pilsen" | "ship" } |
See below |
This is the HTML version's key. Each item is either { type: "product", id, qty } or
{ type: "gift", key, amount, design, to, toEmail, from, message, date }. readBag() drops unknown
product ids, duplicate lines, quantities that aren't whole numbers (and caps them at 10), gift cards
with an amount outside the limits, an unknown design or an invalid email, and trims text to its
maximum length. A cleaned version is written back. Bags open in two tabs stay in sync through the
storage event.
To reset, empty the bag on the Shop page, or run localStorage.removeItem('fade-bag') in the browser
console. Nothing else is stored.
SEO and sharing#
Page titles come from WordPress: the page title, an em dash and the site name (for example
"Barbers — Fade Bros. Barber Co."); 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 x 630 image) as the default social image. WordPress already generates a sitemap at
/wp-sitemap.xml. For local search, add BarberShop structured data with each shop's address,
phone and opening hours; most SEO plugins can add it.
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 (red, brass on dark sections), the menu button with aria-expanded, filter chips with
aria-pressed and live counts, a booking flow that moves focus to each step and a
keyboard-operable calendar, native <details> for the FAQ, form errors linked with
aria-describedby and aria-invalid, alt text or aria-hidden on every picture, and
reduced-motion support for the reveals, the marquee and the stamp. Text meets WCAG AA contrast;
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
Brass text on dark backgrounds only, 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, 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-fade, 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-fade. All text in patterns,
templates and PHP goes through WordPress's translation functions; the script's strings (shop,
service, barber and product names, open-now and wait texts, the booking lines, the bag, form errors
and messages, day and month names) are translated in PHP (freeframe_fade_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-fade wp-content/themes/freeframe-fade/languages/freeframe-fade.potSave freeframe-fade-es_ES.mo (for example) in wp-content/languages/themes/ or in the theme's
languages/ folder. Page content created by Fade 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
with FADE.shop.locale and currency.
Child themes#
Use a child theme for changes you want to keep when Fade is updated (your shops, hours, services,
barbers and products in particular). Create wp-content/themes/fade-child/style.css:
/*
Theme Name: Fade Child
Template: freeframe-fade
*/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-fade) and enqueue yours in the child's functions.php, keeping the inline
window.freeframeFade strings (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 > Fade.
- The wait board says "Closed" during your opening hours. Times are worked out in
FADE.shop.timeZone. Set it to your shop's IANA zone (for exampleAmerica/New_York) and checkhoursinFADE.locations(0 is Sunday). - A barber never shows up in booking. They must have the shop in
scheduleand every skill in the chosen services'needs(or category). Checkskillsspelling against the category ids. - No times on any day. The chosen services may be longer than the barber's shift, or the shift
falls outside the shop's hours. Shorten the services or widen the
schedule. - A new barber card doesn't show up under a filter. Its group needs the classes
ffd-barber--<id>,ffd-locs--<shop ids>andffd-specs--<speciality ids>(several joined with an underscore). See Adding a barber. - Fade 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 > Fade pages.
- The booking flow, the bag or the forms don't send anything. They are demos; see Connecting real bookings and Forms.
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 barber portraits, product art, the storefront,
the map, the stamp and the logo are original SVG artwork included under the same license. Anton and
Archivo are licensed under the SIL Open Font License 1.1 (OFL.txt in each folder of
assets/fonts/). All people, shops and reviews named in the demo are fictional.