What I learned building a WordPress block theme for hair salons (and getting it ready for WordPress.org)
Most small salons I looked at have the same setup: an Instagram account, a booking link (Treatwell, Fresha, Booksy, Planity) and no website of their own. When I asked myself what a salon site actually needs, the list was
Most small salons I looked at have the same setup: an Instagram account, a booking link (Treatwell, Fresha, Booksy, Planity) and no website of their own. When I asked myself what a salon site actually needs, the list was short: prices with durations, the team, recent work, opening hours, and a big "Book" button.
So I built Atelier Mira, a full site editing (block) theme for hair salons and beauty studios, and a free Lite version that I submitted to the WordPress.org theme directory. This post is what I learned on the way.
- Live demo: https://atelier-mira-wp.pages.dev
- Free Lite version (GPL, on GitHub): https://github.com/polyastavreva/atelier-mira-lite
1. A block theme is mostly theme.json
There's no page builder and no options panel. Colors, fonts, spacing and layout widths all live in theme.json, and the owner changes them later in Appearance → Editor → Styles.
I named the palette after things you'd find in a salon rather than "primary/secondary", so the names still make sense to the person editing:
"color": {
"palette": [
{ "slug": "plum", "color": "#2B1B2A", "name": "Plum" },
{ "slug": "rose", "color": "#F3E3DE", "name": "Rose" },
{ "slug": "blush", "color": "#E9C9C0", "name": "Blush" },
{ "slug": "paper", "color": "#FCFAF9", "name": "Paper" },
{ "slug": "olive", "color": "#6E7A4C", "name": "Olive" },
{ "slug": "sage", "color": "#DCE3C9", "name": "Sage" }
]
}
2. Ship the fonts with the theme
Loading Google Fonts from Google's servers is a GDPR problem in Europe, and most of my target salons are in Europe. Block themes can register local font files directly in theme.json, so the browser never contacts a third party:
"fontFamilies": [{
"fontFamily": "\"Instrument Serif\", ui-serif, Georgia, serif",
"slug": "serif",
"name": "Instrument Serif",
"fontFace": [{
"fontFamily": "Instrument Serif",
"fontWeight": "400",
"fontStyle": "normal",
"fontDisplay": "swap",
"src": ["file:./assets/fonts/instrument-serif-latin-400-normal.woff2"]
}]
}]
I split each font into latin and latin-ext files with unicodeRange, so a visitor only downloads the subset the page needs. The OFL licence files sit next to the fonts.
3. A price menu made only from core blocks
The section I care about most is the price list. Clients want three things on one line: the service, how long it takes and the price. I didn't want a custom block for this, because custom blocks are one more thing to maintain and one more thing the salon owner can break.
Each row is a core Group block with three Paragraphs inside. The layout is plain CSS grid on a class:
.am-item {
display: grid;
grid-template-columns: auto 1fr auto; /* name | duration | price */
align-items: center;
gap: 4px 10px;
padding: 16px 0;
border-top: 1px solid var(--wp--preset--color--line);
}
.am-item__time {
justify-self: start;
font-size: 0.8rem;
border: 1px solid var(--wp--preset--color--olive);
border-radius: 999px;
padding: 0 9px;
}
.am-item__price {
text-align: right;
font-variant-numeric: tabular-nums; /* prices line up */
}
To add a service, the owner duplicates a row in the editor and types. tabular-nums is a small thing that makes a long price list look printed instead of typed.
4. Motion that never flashes and respects "reduce motion"
The hero photos rise in one after another, and gallery photos fade in on scroll. Two problems with that kind of animation:
- If the "hidden" state is applied by JavaScript after load, the photos flash visible and then disappear.
- Some visitors ask their system for less motion.
The fix is a tiny inline script in <head> that adds a class before the first paint, and only when the visitor hasn't asked for reduced motion:
function atelier_mira_lite_motion_flag() {
wp_print_inline_script_tag(
"if(!window.matchMedia||!matchMedia('(prefers-reduced-motion: reduce)').matches){document.documentElement.classList.add('am-anim');}"
);
}
add_action( 'wp_head', 'atelier_mira_lite_motion_flag', 1 );
All animation CSS is scoped under .am-anim, so without JavaScript, or with reduced motion on, the photos are simply there:
.am-anim .am-reveal { opacity: 0; transform: translateY(32px); transition: opacity .9s, transform .9s; }
.am-anim .am-reveal.is-in { opacity: 1; transform: none; }
An IntersectionObserver adds is-in when a photo scrolls into view.
5. What the WordPress.org review checks (and what caught me out)
Getting a theme into the official directory is stricter than selling it elsewhere. Things I had to change for the Lite version:
-
Text domain = theme slug. Every
__()andesc_html_e()usesatelier-mira-lite, and you don't callload_theme_textdomain()any more, WordPress loads translations itself. - Images must be GPL-compatible. My paid demo uses Unsplash photos. Those can't go into a GPL theme, so the Lite version ships soft placeholder images I made myself, marked "Your photo", in the right size for each spot.
-
Required templates.
archive.htmlandsearch.htmlneed to exist even on a one-page theme, and the 404 page needs a search form. - Screenshot 1200×900, under the size limit, showing the theme as it really installs.
- Readme credits for every bundled font and image, and at most one link to a paid version.
- Theme Check in WP-CLI catches most of this before a human reviewer does:
wp theme-check run atelier-mira-lite
6. Lite vs Pro
The Lite version is a complete one-page salon site: hero, scrolling services band, studio intro, price menu, opening hours with a booking button, plus blog/archive/search/404 templates.
The paid version adds team, gallery and review sections, an appointment request form, and WooCommerce styling for selling gift cards and products. If you want to see it:
- Gumroad: https://stavreva68.gumroad.com/l/atelier-mira-wordpress-theme
- Etsy: https://www.etsy.com/listing/4604925781/salon-website-template-for-wordpress
If you build sites for salons or other small local businesses, I'd love to hear what sections your clients always ask for. And if you spot something in the Lite code you'd do differently, issues on GitHub are very welcome.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.