Repository navigation
feat(templates): refresh the blog template design - #3951
Conversation
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
|
🚀 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 deploymentPreview Deployments by commit
|
There was a problem hiding this comment.
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:109The
<html>langis 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"}>getI18nConfigis exported from"emdash"and can be added to the existing import block. -
[suggestion]
templates/blog/src/components/PostCard.astro:63Card dates are formatted with a hard-coded
"en-US"locale. Consider deriving the locale fromgetI18nConfig().defaultLocale(falling back to"en-US") so dates match the site language. -
[suggestion]
templates/blog/src/pages/posts/[slug].astro:101Post 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.
…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.
ascorbic
left a comment
There was a problem hiding this comment.
Looks great! I'm going to sync this before updating the templates for 1.2
What does this PR do?
Refreshes the design of the blog template (
templates/blog, synced totemplates/blog-cloudflare) with a neutral, modern look. Every field, route, query, cache hint, and visual-editing attribute is kept.Design system
--color-brand), all throughlight-dark()tokens with the no-light-dark()fallback kept in sync..band) is ruled off with a hairline that meets the rails at a crosshair.Base.astro(.frame,.band,.section,.intro,.eyebrow,.chip,.btn,.post-grid) and in new components:PostCard(card,featureandrowvariants),Byline(inline or list), andPagination.Pages
/posts: rows with end-side thumbnails, bylines, and tag chips./pages/[slug]: a split layout, with the title on the start side and the body on the end side.Fixes that came with the refresh
/_emdash/api/media/file/<avatarMediaId>. That route serves storage keys, so it returned 404avatarStorageKey(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)#8b8b8bon white was about 3.5:1#6b6b6bon#fafafais 5.1:1; dark mode is 6.1:1<time>elements had nodatetimeOther changes worth knowing about:
aria-currenton the active menu item./search.AGENTS-template.mdand both generatedAGENTS.mdfiles describe the new design.Backward compatibility
--color-bg-subtle,--color-border-subtleand--tag-padding-yare kept, and core's table block still reads--color-bg-subtle.--color-on-brandnow follows the scheme (white in light mode, near-black in dark mode), and the docs and thetheme.cssexample set the three brand tokens together.Not included
demos/are not synced.left:-9999px, which makes RTL pages scroll horizontally.Type of change
Checklist
pnpm typecheckpasses (plusastro checkfortemplates/blogandtemplates/blog-cloudflare: 0 errors, 0 warnings)pnpm lintpasses (pnpm lint:json: 0 diagnostics)pnpm testpasses (or targeted tests for my change):tests/repro/blog-post-count.render.test.ts(7/7) andtests/unit/templates(10/10)pnpm formathas been run/postsmarkup contractAI-generated code disclosure
Screenshots / test output
Seeded sample content on the local dev server. README screenshots in
assets/templates/blog/latest/are regenerated.Desktop (1440px)
New layouts (1440px)
Phone (390px, first screen)
Dark mode and the avatar fix
The avatar shot shows a byline with an uploaded avatar; before this change, that avatar's URL returned 404.