Post Page
betaA whole blog post page, built from our website components: a floating navbar, the post with its author and a list of its sections, other posts to read next, a closing band, and a footer.
Preview
How to plan a release
The steps a team can follow from the first list of changes to the day it ships.
A release goes better when everyone knows what is in it, who owns each part, and when it ships. These are the steps a team can follow, from the first list to the day it goes out.
Write down the scope
Start with a short list of what the release changes. Keep each line to one change, and give it one owner.
- New features, each with one owner.
- Fixes that people asked for.
- Anything that changes how people sign in or pay.
Set the dates
Pick the day it ships first, then work back from it. Leave a few days between the last change and the release, for testing.
Move the release, not the testing.
Check the build
Run the checks on the release branch with npm run check. Every check must pass before the build goes out.
Share the release notes
Post the notes where people already look, like the changelog and the team channel, and name who to ask about each line.
More posts.
Product, 4 min read
Shared folders are here
Put files in one folder that the whole team can open, and choose who can change them.
Priya RamanDesign, 6 min read
How we review designs each week
One meeting, a short list, and notes that stay on the design.
Elin SatoEngineering, 9 min read
Making sync faster on large teams
What we changed so a team of a thousand sees edits in under a second.
Hana Novak
Plan your team’s week in one place.
Free for one team. No card needed.
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the template. It also adds every component it uses. Its own files go in your
componentsfolder, and they are yours to edit.npx shadcn@latest add @txbrendan/post-page
Use it for
- One post on a product site's blog. Render it as the whole page, from your app's page file, once per post.
- Pass the post in `article`: its title, date, author, and the post itself as `article.children`, in plain HTML like p, h2, ul, and pre.
- Give the post two or more h2s. On wide screens a list of them sits beside the post.
- Set `article.back` to link to the list of posts, which Blog Page draws.
- Use `more` for up to three other posts, like the newest ones. Leave out the post on this page.
- Pass your router's link once in `renderLink`. From a server page, render the page from a client file, since a server page cannot pass a function. Pass the post's body from the server page as children, so it is not sent as code.
- Set `embedded` when you show the page inside another page, like a gallery, so it has no second main.
Avoid it for
- A second h1. The post's title is the page's h1.
- More than three other posts. Link to the blog for the rest.
Props
- navbar
The Navbar's props. It floats as a pill once the page scrolls, and folds into a menu below 42rem.
NavbarProps
- article
Required. Article's props: `title`, `date`, `author`, `back`, `cover`, and the post as `children`. The title renders as the page's one h1.
ArticleProps
- more
Other posts to read next, as Post Grid cards, under a headline that defaults to "More posts.". Its id defaults to "more".
{ id?: string; headline?: HeadlineProps; posts: Post[] }
- cta
The closing band above the footer.
CallToActionBandProps
- footer
The Footer's props.
FooterProps
- renderLink
Draws every link in the navbar, the back link, the other posts, the closing band, and the footer, unless a part sets its own.
(props: NavbarLinkProps) => ReactNode · default a plain <a>
- embedded
Renders the content in a div, not a main, for a preview inside another page.
boolean · default false
- className
Extra classes on the root, which holds the container query.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| navbar | NavbarProps | - | The Navbar's props. It floats as a pill once the page scrolls, and folds into a menu below 42rem. |
| article | ArticleProps | - | Required. Article's props: `title`, `date`, `author`, `back`, `cover`, and the post as `children`. The title renders as the page's one h1. |
| more | { id?: string; headline?: HeadlineProps; posts: Post[] } | - | Other posts to read next, as Post Grid cards, under a headline that defaults to "More posts.". Its id defaults to "more". |
| cta | CallToActionBandProps | - | The closing band above the footer. |
| footer | FooterProps | - | The Footer's props. |
| renderLink | (props: NavbarLinkProps) => ReactNode | a plain <a> | Draws every link in the navbar, the back link, the other posts, the closing band, and the footer, unless a part sets its own. |
| embedded | boolean | false | Renders the content in a div, not a main, for a preview inside another page. |
| className | string | - | Extra classes on the root, which holds the container query. |
Accessibility
- The content sits in a main landmark, between the navbar's nav and the footer, so skip links and screen readers find it.
- The post's title is the page's one h1. The post's sections and the More posts headline are h2s, and each other post's title an h3.
- Each part keeps its own accessibility, listed on its page.
Examples
A short post page
<PostPage
navbar={{ brand: "Workspace", links: [{ href: "/blog", label: "Blog", current: true }] }}
article={{
back: { href: "/blog", label: "All posts" },
title: "How to plan a release",
date: "2026-10-12",
tag: "Guides",
// The same cover as the post's card on the blog.
cover: <PostGridCover post={{ href: "/blog/plan-a-release", tag: "Guides" }} />,
children: (
<>
<p>A release goes better when everyone knows what is in it.</p>
<h2>Write down the scope</h2>
<p>Start with a short list of what the release changes.</p>
</>
),
}}
more={{ posts: [{ href: "/blog/shared-folders", title: "Shared folders are here", date: "2026-10-28" }] }}
footer={{
brand: "Workspace",
columns: [{ title: "Company", links: [{ href: "/blog", label: "Blog" }] }],
legal: "© 2026 Workspace",
}}
/>