Freeframe

Documentation

Accrue

v1.0.0Updated Oct 30, 2026An editorial, warm website for an accounting and tax firm, with a live tax deadline calendar, a fee estimator and consultation booking.

Overview#

Accrue is a website for an accounting firm, CPA practice or tax preparer, built as a WordPress block theme. The fictional brand is Fernley Whitcomb CPAs, a seven-person firm on Cherry Street in Chattanooga, Tennessee, serving households and businesses on both sides of the Georgia and Alabama state lines. The theme has a home page with a live next-deadline card, firm figures, a numbered list of services, industries, a Tennessee tax note, testimonials and getting-started steps; a services page and one service page that shows any of the seven services; a pricing page with bookkeeping packages and a fee estimator; a federal and Tennessee tax deadline calendar worked out by rule, with a countdown and a saved document checklist; a team page with illustrated portraits; insights with a category filter and an article page; a consultation booking page with a rolling slot grid; 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/accrue.

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 parts that are worked out in the browser (the deadline card and calendar, the checklist, the fee estimator, the service and article bodies, the insights grid and the booking form) work as in the HTML version: they are small Custom HTML blocks driven by assets/js/main.js, which reads the firm's data from assets/js/data.js. Colors and fonts are design tokens in theme.json: change them in Appearance > Editor > Styles and the whole site follows. Every date and time is worked out in America/New_York (Eastern time), whatever the visitor's own time zone is.

Quick start#

Requirements: WordPress 7.1 or newer and PHP 7.4 or newer. No plugins are needed.

  1. Download accrue.zip from freeframe.dev/templates/accrue (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 Accrue.
  3. Go to Appearance > Accrue setup and click Create the demo pages. This creates the Home, Services, Individual tax returns (the service page), Pricing and fee estimator, Tax deadline calendar and document checklist, Our team, Insights, the article page and Book a consultation 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 > Accrue pages) made of section patterns. Every section is also offered on its own in the block inserter under Patterns > Accrue.

Page Slug Page pattern Sections (pattern files in patterns/)
Home / page-home.php home-hero (with the next-deadline card), firm-numbers, home-services, who-we-serve, tennessee, testimonials, getting-started (with the first open consultation time), home-cta
Services /services/ page-services.php services-header, services-grid (cards built from SERVICES), services-fit, engagement, services-cta
Individual tax returns /service/ page-service.php service-detail (filled from SERVICES by ?s=), service-cta
Pricing and fee estimator /pricing/ page-pricing.php pricing-header, bookkeeping-packages, fee-estimator, return-prices, other-prices, pricing-cta
Tax deadline calendar and document checklist /deadlines/ page-deadlines.php deadline-calendar (countdown, filters, year switch, month list, holidays), document-checklist, deadlines-cta
Our team /team/ page-team.php team-header, partners, staff, credentials, team-cta
Insights /insights/ page-insights.php insights-list (category filter, featured article and grid built from ARTICLES), deadline-cta
When an S Corporation Election Makes Sense /article/ page-article.php article (filled from ARTICLES by ?a=)
Book a consultation /contact/ page-contact.php contact-header, booking (form, success panel, office hours and contact details), visit (map and directions), client-portal (#portal)
404 any missing URL templates/404.html 404

The service and article pages work as in the HTML version: one page each, filled in the browser from assets/js/data.js. /service/?s=payroll shows Payroll, /article/?a=working-across-the-state-line shows that article; with no parameter they show the most-booked service and the latest article, and an unknown slug shows them with a notice. Every link the theme writes to a service or article uses these addresses. Posts use templates/single.html; the blog, archives and search results use templates/index.html, templates/archive.html and templates/search.html.

Project structure#

freeframe-accrue/
  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 scripts (with the script's strings, page and image URLs),
                            editor styles, the `js` class script, theme color 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 > Accrue 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/data.js         the firm's data: FIRM, SERVICES, DEADLINE_RULES, CHECKLISTS, ESTIMATOR,
                            BOOKING, CATEGORIES, ARTICLES (window.ACCRUE)
  assets/js/main.js         deadlines, countdowns, checklist, estimator, service and article pages,
                            insights filter, booking, header, reveal and back to top
  assets/icons/             SVG icons for the Icon block
  assets/img/services/      one illustration per service (480 × 300)
  assets/img/articles/      one cover per article (640 × 400)
  assets/img/               favicon (the logo mark), social image
  assets/fonts/             Hahmlet and Geologica, 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-accrue.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

Customizing#

Brand and colors#

The colors are in theme.json (settings.color.palette) and in Appearance > Editor > Styles > Colors > Palette:

Color Default Used for
Paper #f5f1e6 the page background (oat linen)
Paper 2 #ebe5d3 alternate bands, past deadlines
Card #fffcf4 cards and forms
Line, Line strong #d9d2bd, #b9b096 hairlines and field borders
Ink #1f2316 text and dark bands (olive black)
Ink 2, Ink 3 #2c3220, #3a4129 raised panels and borders on ink
Muted #575a48 secondary text on light backgrounds
Sage #c9ceb1 secondary text on ink
Olive #4f5b1c buttons, links, the logo
Olive deep, Olive soft #3b4513, #e1e5c8 hover, tints and highlighted cards
Marigold #e8b13a highlighter strokes and accent buttons (always with ink text)
Marigold deep, Marigold soft #d49a1f, #f8e7bb numbers, quote marks, the "next" deadline
Due, Due soft #9a3b1e, #f6dccf errors, "due soon" tags, moved-date notes
Done #3d6b2f ready states
White, Black for your own blocks

The theme's stylesheet reads these colors from the palette (bg-olive, text-muted...), so changing Olive in Styles recolors every button and link at once. Keep Muted dark enough for text on Paper (4.5:1) and never use Marigold for text on a light background. The SVG illustrations, the portraits, the map and the 404 drawing carry their own colors in their files and markup; the logo mark is assets/img/favicon.svg.

Components worth knowing in src/theme.css: folder (the file-folder card with a tab, the template's motif; folder-ink and folder-olive variants), mark (the marigold highlighter under a word in a headline), display and section-title (headline sizes), eyebrow and kicker (labels), on-dark (light text and marigold accents on an ink or olive band), card, tag, chip, receipt (the zigzag edge under the estimator total). Add them to a block in Block settings

Advanced > 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: Hahmlet (500 to 700; preset serif) for headlines, numbers in cards and the team initials, and Geologica (300 to 700; 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, --sans), so keep those slugs. The map on the contact page names Geologica directly (font-family="Geologica, sans-serif").

Font sizes are the presets in theme.json (xs 0.75rem to 9xl 8rem, the Tailwind scale). Figures use tabular, lining numerals through the num class.

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 > Accrue.

What the script fills in or 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
home-hero the next-deadline card see Countdown and next-deadline line
who-we-serve the six industry icons small SVG drawings
partners, staff the portraits illustrated SVG portraits (see Images and artwork)
services-grid the empty list the service cards are built into see Service pages
service-detail the breadcrumb, the notice, the picture and the empty lists for what's included, who it's for, the timeline, the FAQ and related services see Service pages
fee-estimator the form and the result panel see Fee estimator
return-prices the price table edit the rows in HTML view
deadline-calendar the countdown card, the filter chips and year switch, the month list, the holiday list see Deadline calendar
document-checklist the list buttons and the checklist card see Document checklist
insights-list the category chips, the featured article and the grid see Insights and articles
article the breadcrumb, the notice, the cover, the body and "Keep reading" see Insights and articles
booking the form, its success panel and the office hours table see Consultation booking and Forms
visit the map an SVG map of downtown Chattanooga
404 the "misfiled" drawing an SVG drawing
header the menu button 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, so the script turns classes starting with ffd- into them when the page loads: ffd-svc-name on the service page's heading becomes data-svc-name, and ff-hidden becomes the hidden attribute. Keep those classes when you restyle a block.

  • reveal fades a block in as it scrolls into view.
  • link-card on a group makes the whole group clickable: the first link inside it stretches over the group (the logo in the header and footer).

Everything data-driven is in assets/js/data.js, inside window.ACCRUE, the same file as in the HTML version:

Constant What it holds
FIRM name, phone ((423) 555-0138), phoneHref, email (hello@fernleywhitcomb.example), timeZone (America/New_York), refPrefix (FW)
SERVICES seven services keyed by slug: name, short, from (price line), art, intro, included[], forWho[], timeline[{ when, step }], faq[{ q, a }], related[] (slugs)
SERVICE_ORDER display order: individual-tax, business-tax, bookkeeping, payroll, advisory, nonprofit, irs-notices
DEFAULT_SERVICE individual-tax, shown when ?s= is missing or unknown
TAXPAYER_TYPES calendar filters: all, individual, employer, passthrough, ccorp, nonprofit
DEADLINE_RULES 24 deadline rules (see Deadline calendar)
CHECKLISTS, CHECKLIST_ORDER four lists: individual (11 items), selfemployed (11), business (11), nonprofit (9)
ESTIMATOR the fee estimator's prices and limits (see Fee estimator)
BOOKING consultation slots, days shown, closures, meeting types (see Consultation booking)
CATEGORIES article categories: all, tennessee, individuals, self-employed, small-business, nonprofits, irs-notices
ARTICLES seven articles, newest first: slug, title, date (YYYY-MM-DD), cat, author, mins, art, dek, body[]

Change data.js in a child theme (see Child themes) so an update doesn't overwrite it. The home page's numbered service list, the pricing packages and the footer's service links are blocks with the same names and prices; when you rename a service, update those too.

Adding a service

js
// assets/js/data.js, inside SERVICES
'estate-tax': {
  name: 'Trusts & estates',
  short: 'Fiduciary returns and estate tax filings for executors and trustees.',
  from: 'From $1,200 a return',
  art: 'assets/img/services/estate-tax.svg',
  intro: '…',
  included: ['…'], forWho: ['…'],
  timeline: [{ when: 'Month 1', step: '…' }],
  faq: [{ q: '…', a: '…' }],
  related: ['individual-tax', 'advisory']
}

Then add 'estate-tax' to SERVICE_ORDER and put estate-tax.svg in assets/img/services/ (the theme passes the URL of every SVG in that folder and in assets/img/articles/ to the script, keyed by the art path). The service appears on the Services page, in the booking form's service menu and at /service/?s=estate-tax. Article bodies are arrays of blocks: { p: '…' }, { h2: '…' }, { ul: ['…', '…'] } or { quote: '…' }. Keep ARTICLES newest first: the first one is the featured article and the fallback.

Images and artwork#

All art is SVG drawn for this template; there are no photos.

  • assets/img/services/<slug>.svg: 480 × 300 (8:5). Replace with photos at the same ratio (and change the art path in data.js); cards crop with object-cover.
  • assets/img/articles/<name>.svg: 640 × 400 (8:5); the article page shows it cropped to 16:7.
  • The team portraits are inline SVG in Custom HTML blocks on the Team page (inline so their initials use Hahmlet; each has role="img" and an aria-label). To use a photo, replace the block with an Image block at 6:7 (staff) or filling the 13rem column (partners), with real alt text.
  • The industry icons on the home page, the map and the 404 drawing are inline SVG in Custom HTML blocks; replace any of them with an Image block if you prefer.
  • Icons are Icon blocks: select one and pick another from the Accrue 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 (the logo mark) is used until you set a Site Icon in Settings > General.

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 pattern starts with the dark top bar: its first line is replaced by the next tax deadline when the page loads (the link written there is what shows without JavaScript).

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, Pricing, Deadlines, Team, Insights, Contact). WordPress marks the current page's link with aria-current="page"; on the service page the script marks Services, and on the article page Insights, as the HTML version does (the page URLs are in freeframe_accrue_page_urls() in inc/setup.php). A Navigation block that doesn't point at a menu shows the most recently created one; to pin a menu, select the Navigation block and choose it in the block settings, for both blocks.

The footer's Services and The firm menus are List blocks: click a link to change it, press Enter to add one. The copyright year is written by PHP. The phone number and email appear in the top bar, the footer, the contact page and the call-to-action bands; search for them when you change them (and in FIRM in data.js).

Features#

Deadline calendar#

The deadline calendar page, code in sections 4 and 7 of assets/js/main.js (deadlines(y), businessDay(), paintDeadlines()). Each entry in DEADLINE_RULES is a rule, not a date:

js
{ id: 'individual', short: 'Individual returns', month: 4, day: 15, rule: 'federal',
  who: ['individual'], title: 'Individual returns for {prev}, or an extension; …', forms: 'Form 1040, Form 4868' }

For every calendar year the page builds the date from month and day, then moves it to the next business day:

  • rule: 'federal': past Saturdays, Sundays, federal holidays and DC Emancipation Day (April 16, observed on the Friday before or Monday after when it falls on a weekend). April 15, 2028 (a Saturday) moves to Tuesday, April 18, 2028: Monday April 17 is the observed Emancipation Day.
  • rule: 'tn': past Saturdays, Sundays, federal holidays (not Columbus Day or Emancipation Day) and Good Friday, for Tennessee franchise & excise deadlines.

{prev} and {year} in titles become the previous year and the calendar year. Moved deadlines show a note such as "Moved from Sat, Oct 31: weekend." Holidays are computed by holidays(y) for any year (fixed-date holidays observed on the Friday before a Saturday and the Monday after a Sunday, Good Friday from Easter); which ones move deadlines is set by HOLIDAY_SETS in main.js.

The calendar opens on the year of the next deadline; the year switch shows the current and the next year. Each entry is marked Passed, Next, or "in N days" (red within 30 days). A deadline counts as ahead until midnight Eastern on its due date. The filter chips (data-type-filter) and year buttons (data-year-offset) are buttons with aria-pressed; the count line (data-dl-count) is a polite live region. Choices are written to the URL (?type=, ?year=).

Expected values at Friday, October 30, 2026, 10:00 AM Eastern:

  • Count line: "24 deadlines in 2026 · 4 still ahead".
  • Next deadline: Monday, November 2, 2026, "Third-quarter payroll return", Form 941 · IRS, moved from Sat, Oct 31; countdown 3 days, 14 hours, 0 minutes. Then Mon, Nov 16 and Tue, Dec 15 (two).
  • With ?type=individual: "6 deadlines in 2026 · 0 still ahead"; next is Friday, January 15, 2027.

To turn a deadline off, delete its rule. To add a fiscal-year deadline, add a rule with its own month and day. Nothing here goes stale: the dates are worked out for whatever year it is.

Countdown and next-deadline line#

Every page's top bar shows the next deadline for everyone, for example "Next deadline Mon, Nov 2 · Form 941 (Q3) in 3 days" (the header paragraph with the class ffd-next-deadline). The home page card (data-deadline-teaser) shows the next deadline's date, label, detail, days and hours left, and the three after it. The calendar page has the full countdown (data-dl-next: days, hours, minutes until 11:59 PM Eastern on the due date, with an sr-only sentence for screen readers; the big numbers are aria-hidden). All of them refresh every 30 seconds, except when ?now= is set.

Document checklist#

The checklist on the deadline calendar page (/deadlines/#checklist), section 8 of main.js. Four lists from CHECKLISTS, switched with the buttons [data-checklist-tab] (or ?list=<id> on load). Each item is a real checkbox inside a <label>. The progress text (data-check-progress, for example "1 of 11 ready") is a polite live region. "Clear this list" (data-check-reset) unticks the current list only; when every item is ticked, a "Everything's here" note appears. Ticks are saved in the browser (see Saved data).

Fee estimator#

The fee estimator on the pricing page (/pricing/#estimator), section 10 of main.js (estimate(inp)). Settings in ESTIMATOR:

Setting Value
entities sole, partnership ($1,440 a year), scorp ($1,560 a year + $40/month), ccorp ($1,800 a year), nonprofit ($1,680 a year + $100/month)
tiers Foundation up to 100 transactions $350, Standard up to 250 $525, Expanded up to 500 $775, Full-charge up to 1,000 $1,150
overBlock, overBlockFee above 1,000: $95 for each further 250 transactions or part of 250
payrollBase, payrollPerEmployee $65 + $9 per employee, when there is at least one employee
extraStateFee $45 a month for each state after Tennessee
cleanupPerMonth $175 per month behind, a one-time fee shown separately
limits transactions 0–2,000 in steps of 25, employees 0–60, states 1–6, cleanup 0–24 months
defaults scorp, 200 transactions, 4 employees, 1 state, 0 months

Monthly estimate = bookkeeping tier + payroll + extra states + entity extra + annual return fee ÷ 12, rounded to the dollar. Defaults give $796/month ("$9,552 a year"); /pricing/?entity=nonprofit&tx=1300&emp=12&states=3&cleanup=6 gives $1,843/month with "$1,050 once, for 6 months of catch-up". Every change rewrites the URL, so an estimate can be shared; invalid parameters fall back to the defaults. The package cards above the estimator are blocks: when you change tiers, change their prices too.

Service pages#

/service/?s=<slug>, section 9 of main.js. Valid slugs are the keys of SERVICES. The slug is checked with Object.prototype.hasOwnProperty.call(SERVICES, slug); a missing slug shows DEFAULT_SERVICE, an unknown one shows it with the notice "We couldn't find a service called …". The page title, breadcrumb, heading, intro, price line, picture, booking links (/contact/?service=<slug>), what's included, who it's for, the timeline, the FAQ (native <details>) and related services are filled in from the data. The Services page builds its grid of cards into data-services-grid, followed by a dark "Not sure which?" tile.

Insights and articles#

On the Insights page the category chips (data-cat-filter) filter ARTICLES; with "All" the newest article is the large featured card and the others are in the grid. The count reads "7 articles" or, for example, "2 articles in Small business". ?cat=<key> sets it on load and is kept in the URL. /article/?a=<slug> shows an article (unknown or missing slugs show the newest, with a notice when a slug was given); "Keep reading" lists three other articles, same category first. Articles keep their real publication dates; they are past content and never move.

Consultation booking#

The booking page, section 12 of main.js. Settings in BOOKING:

Setting Value
slots Monday–Thursday 9, 10, 11 AM, 1, 2, 3, 4 PM; Friday 9, 10, 11 AM, 1, 2 PM; Saturday 9, 10, 11 AM (tax season only); lunch 12–1
taxSeason 02-01 to 04-15 (Saturdays are offered in this window)
minutes 30
days 10 open days, starting tomorrow (Eastern)
demoTaken 45: the share of slots shown as already booked, stable for each date and time
closures the office holidays (observed dates)
meetings office, video, phone

Sundays, Saturdays outside tax season and closure days are skipped, so the grid always shows the next ten open days. /contact/?service=<slug> preselects a service (?service=not-sure selects "Not sure yet"). The first open time on the home and contact pages (data-first-slot) is the earliest free slot: at 10:00 AM Eastern on Friday, October 30, 2026, Mon, Nov 2 at 9:00 AM. The office status ("Open now", "Closed now", "Closed today for …") uses the hours in section 13 of main.js; the hours table is in the booking pattern. When you connect a real calendar, replace slotTaken() with its availability.

Previewing a date#

Add ?now=YYYY-MM-DDTHH:MM (Eastern wall-clock time) to any page to see the site at that moment: the top bar, countdowns, calendar, office status, first open time and booking grid all use it, and live refresh stops. Examples: /deadlines/?now=2028-04-01T10:00&year=2028 (the April 18, 2028 shift), /contact/?now=2027-02-25T09:00 (tax-season Saturdays in the grid).

Header, mobile menu, reveal and back to top#

The header is sticky and gains a shadow after scrolling. Below 1024 px the menu button (#menu-toggle, aria-expanded, aria-controls="mobile-menu") opens the mobile menu; Escape closes it and returns focus to the button, and it also closes on a link click or when the window grows. Blocks with reveal fade in as they scroll into view (off 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 olive button), Marigold, Outline, Outline (on dark)

The class is-small on a Button block makes it the smaller header size, and num gives it tabular figures (phone numbers).

Forms#

The only form that collects details is the consultation request on the booking page (form[data-booking], in the booking pattern's Custom HTML). The fee estimator is a form for layout only; it never submits. The booking form doesn't send anything until you connect it.

Validation (inline errors with aria-invalid): a service is required; a time is required ("Choose a day and a time."); the name needs at least two characters; the email must look like an address; the phone is optional but, if given, needs 10 digits (or 11 starting with 1). On success the form is replaced by the success panel: "Thank you, <first name>. Your consultation request is in.", a reference FW- + five characters (a hash of name, email, date and time; for "Junie Example", "junie@example.com", Nov 2 at 9:00 AM it is FW-BJRFB), the time, service and meeting type, and what happens next. Focus moves to the success heading.

To connect it, for example to Formspree, replace the success branch in the submit handler in assets/js/main.js (look for "To connect a real backend"; in a child theme):

js
const res = await fetch('https://formspree.io/f/your-id', {
  method: 'POST',
  body: new FormData(bookForm),
  headers: { Accept: 'application/json' }
});
if (!res.ok) { /* show an error message and keep the form */ return; }
// then show [data-booking-success] as the demo does

Make the listener async. The fields are service, meeting, slot_date (YYYY-MM-DD), slot_time (HH:MM, Eastern), name, email, phone, client (new or existing) and notes. Show the success state only when the service answers OK. You can also replace the form's Custom HTML block with a form plugin's block; add the classes label, field and btn-olive to its labels, fields and button so it matches.

Saved data (localStorage)#

Key Shape Used by
accrue:checklist { "list": "individual", "ticks": { "individual": ["i-w2"], "selfemployed": [], "business": [], "nonprofit": [] } } the document checklist

This is the HTML version's key. On read (readChecklist()), list must be an own key of CHECKLISTS, ticks lists that aren't arrays are ignored, and only item ids that exist in that list are kept, so stale or tampered data can't break the page. Before each change the saved value is re-read, so two open tabs don't overwrite each other's lists. All storage goes through one safeStorage object: localStorage, or an in-memory stand-in when the browser blocks storage, in which case ticks last for the page view. To reset, use "Clear this list", or remove the key in your browser's developer tools (Application > Local Storage). Nothing else is stored; the estimator keeps its state in the URL.

SEO and sharing#

Page titles come from WordPress: the page title, an em dash and the site name (for example "Our team — Fernley Whitcomb CPAs"); the home page shows the site name and tagline (Settings > General). The service and article pages also set document.title for the chosen service or article. 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. If search traffic to individual services or articles matters, give each its own page (or post) instead of the ?s= and ?a= pages, and add AccountingService structured data with your real address and 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 outlines (olive on light, marigold on dark), the menu button with aria-expanded, filter chips, the year switch, checklist tabs and booking days and times as buttons with aria-pressed, booked times disabled and announced as "booked", live regions for the deadline count, checklist progress, estimator result and chosen booking time, countdown digits hidden from screen readers with a sentence instead, form errors linked with aria-describedby and aria-invalid, and reduced-motion support for every animation. 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, and keep aria-controls and aria-labelledby ids in step.

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; keep each class name whole in the script (not built from pieces). 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-accrue, 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-accrue. All text in patterns, templates and PHP goes through WordPress's translation functions; the script's own strings (day and month names, holiday names, deadline notes, estimator lines, booking labels, form errors and messages) are translated in PHP (freeframe_accrue_script_strings() in inc/setup.php). The firm's content in assets/js/data.js (services, deadline titles, checklist items, articles) is edited there directly. Create a translation with Loco Translate, or with WP-CLI and Poedit:

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

Save freeframe-accrue-es_ES.mo (for example) in wp-content/languages/themes/ or in the theme's languages/ folder. Page content created by Accrue setup is copied into your pages in the site's language at that moment; translate it in the editor like any other content. Amounts are formatted in US dollars and dates in US style by the script (money(), shortDate(), longDate()).

Child themes#

Use a child theme for changes you want to keep when Accrue is updated (your services, prices, deadlines and articles in particular). Create wp-content/themes/accrue-child/style.css:

css
/*
Theme Name: Accrue Child
Template: freeframe-accrue
*/

Templates, parts, patterns and theme.json in the child theme override the parent's files of the same name. To use your own data.js, dequeue the parent's data script (freeframe-accrue-data) and enqueue yours under the same handle in the child's functions.php:

add_action( 'wp_enqueue_scripts', function () {
	wp_deregister_script( 'freeframe-accrue-data' );
	wp_register_script( 'freeframe-accrue-data', get_stylesheet_directory_uri() . '/data.js', array(), '1.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
}, 15 );

Do the same for assets/js/main.js (handle freeframe-accrue), keeping the inline window.freeframeAccrue strings and URLs (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 > Accrue.
  • The calendar shows the wrong year or "Passed" everywhere. The page uses the visitor's clock in Eastern time. Check for a leftover ?now= in the URL; it freezes the date.
  • A deadline is on a different date than the IRS announced. Disaster relief and one-off IRS notices can postpone deadlines for some areas. Those aren't rules; add a note to the page or temporarily change the rule's month and day.
  • Service or article links go to the wrong page. The script links to /service/, /article/, /contact/ and the other demo slugs. If you renamed those pages, change their URLs in freeframe_accrue_page_urls() in inc/setup.php (in a child theme, filter the inline script).
  • A new service has no picture. Put its SVG in assets/img/services/ with the name used in its art path; only files in that folder and assets/img/articles/ are passed to the script.
  • Checklist ticks disappear after a reload. The browser is blocking storage (private mode, a sandboxed preview, strict privacy settings); ticks then last for the page view only.
  • Accrue 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 > Accrue pages.
  • A page shows the title twice or no title. Pages use a template without a title, because the page patterns start with their own heading. For a plain text page (privacy policy, terms), choose the Page with title template in the page's settings.

License#

Split license: the PHP code and block markup are licensed under the GNU GPL, version 2 or later, and the CSS, JavaScript, images and design under the Freeframe Commercial License (unlimited sites for you and your clients; no resale or redistribution as a theme). The full terms, with your licensee details, are in LICENSE.txt and at https://freeframe.dev/license#commercial. Third-party components keep their own licenses. Fonts: Hahmlet and Geologica are licensed under the SIL Open Font License 1.1 (OFL.txt in each folder of assets/fonts/). Fernley Whitcomb CPAs, its people, clients, testimonials, phone number and email address are fictional placeholders.

Search documentation

Search the guides and every template's documentation