Skip to content

feat(templates): refresh the blog template design - #3951

Merged
ascorbic merged 11 commits into
mainfrom
noah/block-template-refinement-90f357
Oct 7, 2026
Merged

ascorbic merged 11 commits into
mainfrom
noah/block-template-refinement-90f357

Conversation

@khoinguyenpham04

@khoinguyenpham04 khoinguyenpham04 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Refreshes the design of the blog template (templates/blog, synced to templates/blog-cloudflare) with a neutral, modern look. Every field, route, query, cache hint, and visual-editing attribute is kept.

Design system

  • Neutral palette: off-white canvas, white surfaces, and ink as the brand colour (--color-brand), all through light-dark() tokens with the no-light-dark() fallback kept in sync.
  • A framed layout: hairline rails run the height of the page, and each full-width section (.band) is ruled off with a hairline that meets the rails at a crosshair.
  • White ringed cards with inset media and concentric radii, mono uppercase labels, and pill buttons and chips.
  • Shared building blocks live in Base.astro (.frame, .band, .section, .intro, .eyebrow, .chip, .btn, .post-grid) and in new components: PostCard (card, feature and row variants), Byline (inline or list), and Pagination.

Pages

  • Home: an intro with the site title and the tagline as a muted second line, plus an RSS link. Below it, the featured post as a split card and the latest posts grid.
  • /posts: rows with end-side thumbnails, bylines, and tag chips.
  • Post page: keeps the three-column reading layout (meta rail | body | table of contents and widgets) with a Mintlify-style header and refined prose typography. Comments and widgets are restyled to match.
  • /pages/[slug]: a split layout, with the title on the start side and the body on the end side.
  • Category and tag pages: a card grid with an intro.
  • Search: a large search field and results in a single panel, with matched title terms highlighted.
  • 404: a centred page with actions and a search field.

Fixes that came with the refresh

Problem before Now
Byline avatars were built as /_emdash/api/media/file/<avatarMediaId>. That route serves storage keys, so it returned 404 Avatars use avatarStorageKey (already joined by the loader, so no extra query) or the public media URL. Verified locally: the old URL returns 404, the new one returns 200 (screenshot below)
Muted text #8b8b8b on white was about 3.5:1 #6b6b6b on #fafafa is 5.1:1; dark mode is 6.1:1
Sidebar widgets disappeared below 1100px They render under the article in two balanced columns
A sidebar taller than the window could never be fully seen The table of contents and widgets share one sticky rail that scrolls on its own when it is taller than the window
Recent-post titles and dates ran together in the widget The date sits on its own line
<time> elements had no datetime They do

Other changes worth knowing about:

  • Header:
    • A skip link.
    • aria-current on the active menu item.
    • Long menus wrap rather than widening the page.
    • On phones the menu moves to its own row and search becomes a link to /search.
  • Tag page: leaves the current tag out of each card's chips.
  • Template docs: AGENTS-template.md and both generated AGENTS.md files describe the new design.

Backward compatibility

  • Every existing token name still exists, so theme overrides keep working. --color-bg-subtle, --color-border-subtle and --tag-padding-y are kept, and core's table block still reads --color-bg-subtle.
  • --color-on-brand now follows the scheme (white in light mode, near-black in dark mode), and the docs and the theme.css example set the three brand tokens together.

Not included

  • Demos under demos/ are not synced.
  • Two core issues found along the way are left for separate PRs:
    • The comment form's honeypot is positioned at left:-9999px, which makes RTL pages scroll horizontally.
    • Search snippets are always cut from the first searchable field (the title).

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes (plus astro check for templates/blog and templates/blog-cloudflare: 0 errors, 0 warnings)
  • pnpm lint passes (pnpm lint:json: 0 diagnostics)
  • pnpm test passes (or targeted tests for my change): tests/repro/blog-post-count.render.test.ts (7/7) and tests/unit/templates (10/10)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable): n/a, a visual template change; the existing archive render test covers the /posts markup contract
  • User-visible strings in the admin UI are wrapped for translation (if applicable): n/a, no admin UI changes
  • I have added and reviewed the user-facing changeset (if this PR changes a published package): n/a, templates are not published packages
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...
  • I have included screenshots below if this PR changes the UI

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 5.5

Screenshots / test output

Seeded sample content on the local dev server. README screenshots in assets/templates/blog/latest/ are regenerated.

Desktop (1440px)

Before After
Home Home page before: blue accent, a full-bleed featured image on the left, and a plain grid of posts below Home page after: framed layout with rails, a two-tone site title and tagline intro, the featured post in a split card, and a grid of ringed cards
Post Post page before: hero image, three columns with plain meta labels, and the table of contents and widgets on the right Post page after: back link, tag chip, large title, the hero image in the frame, then the meta rail with avatars, the body, and a sticky rail with the table of contents and widgets
All posts All posts before: a single text column of posts without images All posts after: an archive intro and rows with titles, excerpts, chips, and end-side thumbnails
About About page before: a centred title and two paragraphs About page after: the title on the start side and the body on the end side

New layouts (1440px)

Search Tag
Search results after: a large search field and results in a white panel with the matched title term highlighted Tag page after: a tag intro and a grid of post cards

Phone (390px, first screen)

Post before Post after Home after
Post page on a 390px phone before: a stacked header with a full-width search box, the hero image, then the bylines and title Post page on a 390px phone after: the menu on its own row under the logo, the tag chip, title, excerpt, bylines and date Home page on a 390px phone after: the menu row, the two-tone intro, an RSS button, and the top of the featured post card

Dark mode and the avatar fix

Dark mode Avatar fix
Post reading layout in dark mode: the meta rail, the body, and the table of contents with widgets on a near-black canvas Post meta rail showing an uploaded avatar for EmDash Editorial and initials for Guest Contributor

The avatar shot shows a byline with an uploaded avatar; before this change, that avatar's URL returned 404.

Neutral palette, a framed layout with hairline rails, ringed cards with
inset media, two-tone page intros, and mono labels across every page.

- Shared PostCard (card, feature, row), Byline and Pagination components
- Post page keeps the meta | body | TOC rails; widgets stay visible under
  the article below 1100px and the end rail no longer overflows
- /posts lists rows with thumbnails; category and tag pages use the grid
- Fixes byline avatars (avatarStorageKey), muted text contrast, missing
  <time datetime>, run-together recent-post dates, and Enter in the
  header search now opens /search

Queries, fields and visual-editing attributes are unchanged.
- Header: long menus wrap instead of widening the page, long site titles
  ellipsize, controls scale with text size, and the ⌘K hint no longer
  covers the search icon in RTL
- Header search: Enter no longer jumps to the posts-only /search page, and
  live search stays available down to phone widths
- Post page: the TOC and sidebar widgets share one sticky rail that scrolls
  on its own when taller than the viewport, as the widgets did before
- Long words in headings and long tags no longer overflow on phones
- Forced-colors focus indicators on comment, widget and result inputs
- prose.css drops rules EmDash's blocks never receive and themes the table
  block's header and stripes
- Long tags truncate inside card footers and headers instead of widening
  the page; long search-result paths and page titles shrink to fit
- The post rail scrolls with the page instead of trapping the wheel, and
  leaves no empty gap under the article when the sidebar has no widgets
- Pagination and feature-card arrows mirror in RTL
- theme.css example sets the brand colour's hover and text tokens too
- The 404 search button stays a circle inside its field
- Clamped card excerpts show in full while being edited in place
- Search landmarks get distinct labels, and menu links with a query
  string only count as current on an exact match
- theme.css example keeps card and image radii concentric
@changeset-bot

changeset-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a9b3767

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://noah-block-template-refinement-90f357.try.emdashcms.com, https://noah-block-template-refinement-90f357-emdash-playground.emdash-cms.workers.dev (commit a9b3767)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://7024278d.try.emdashcms.com, https://7024278d-emdash-playground.emdash-cms.workers.dev a9b3767 2026-10-06T22:51:28.250Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://1e3e747a.try.emdashcms.com, https://1e3e747a-emdash-playground.emdash-cms.workers.dev 7c6f90b 2026-10-06T21:36:06.479Z Visit the dashboard ↗

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a sensible, well-scoped design refresh: it keeps every route, query shape, cache hint, and visual-editing attribute while modernizing the blog template’s look and fixing several real UX/accessibility issues (avatar URL, contrast, sticky rail, datetime, skip link, aria-current, RTL icons). The switch from per-post term lookups to getTermsForEntries() keeps logged-out routes efficient, and the CSS is largely logical-properties-first.

I read the full diff, the refreshed templates/blog source and its blog-cloudflare mirror (the 14 source files are identical between the two), the relevant emdash query helpers, the media file route, and the existing render/unit tests. I did not find any blocking bugs, security issues, or AGENTS.md violations. A couple of small, non-blocking suggestions below: the template still hard-codes a few English/locale assumptions that could easily follow EmDash’s i18n config while you have the files open.


Findings

  • [suggestion] templates/blog/src/layouts/Base.astro:109

    The <html> lang is hard-coded to "en". Since EmDash already has i18n configuration, this should follow the configured default locale so screen readers and search engines get the right language for non-English sites while still falling back to English.

    <html lang={getI18nConfig()?.defaultLocale ?? "en"}>
    

    getI18nConfig is exported from "emdash" and can be added to the existing import block.

  • [suggestion] templates/blog/src/components/PostCard.astro:63

    Card dates are formatted with a hard-coded "en-US" locale. Consider deriving the locale from getI18nConfig().defaultLocale (falling back to "en-US") so dates match the site language.

  • [suggestion] templates/blog/src/pages/posts/[slug].astro:101

    Post detail dates also hard-code "en-US". Use the same site locale used elsewhere (or a shared helper) so the published date formatting is consistent with the configured language.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Oct 6, 2026
…typing

The redesign put scroll-padding-top on html to keep fragment targets
clear of the sticky header. Browsers also apply it when revealing a
focused field, and the header search always sits inside that padding,
so focusing it scrolled the page up and every keystroke scrolled it
up again.

Move the offset to :target, which covers the table of contents and the
skip link without touching the header's own controls.
- Long table-of-contents entries wrap instead of giving the sticky rail
  a horizontal scrollbar.
- Search result titles with long words wrap inside their column at
  narrow widths.
- The home tagline and intro leads wrap long words.
- Fragment targets clear the header's measured height, so they stay
  visible when a long menu wraps the sticky header onto more rows.
- The current menu item and the active theme button are outlined in
  forced-colors mode.
- The docs note that the table of contents is hidden below 1100px and
  why the brand tokens are set together.
Deep links opened on page load could land under a wrapped sticky
header, because the header height was measured by a bundled module
script that runs after the browser's first scroll to the fragment.
Measure it in an inline script right after the header, and keep a
ResizeObserver for later changes.

Long words in the post sidebar widgets' flex rows now wrap instead of
widening the rail.
@github-actions github-actions Bot added review/needs-rereview New commits since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Oct 6, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I'm going to sync this before updating the templates for 1.2

@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview New commits since the last review labels Oct 7, 2026
@ascorbic
ascorbic merged commit 8f70ee7 into main Oct 7, 2026
37 checks passed
@ascorbic
ascorbic deleted the noah/block-template-refinement-90f357 branch October 7, 2026 05:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants