Blog Page
betaA whole blog index, built from our website components: a floating navbar, a page title, every post with the newest drawn wide and a row of tags to narrow them, a closing band, and a footer.
Preview
The blog. What we ship, and how we work.
Product news, guides, and notes from the team.
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 SatoProduct, 3 min read
Search now looks inside files
Find a document by a word in it, not only by its name.
Tomás OkaforEngineering, 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 NovakCompany, 2 min read
We opened a second office
More room for the team, and a place to meet the people we work with.
Marcus BellDesign, 7 min read
Picking colors that pass contrast in light and dark
How we check every pair of colors before it ships.
Elin SatoEngineering, 6 min read
How to plan a release
The steps a team can follow from the first list of changes to the day it ships.
Tomás Okafor
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/blog-page
Use it for
- The list of posts on a product site's blog. Render it as the whole page, from your app's page file.
- Write every word in the props: one object for the navbar, the header, the posts, the closing band, and the footer.
- Pass the posts newest first, each with an `href` to its own page. Post Page draws that page.
- Give each post one `tag`, so the row of tags can narrow the list. Set `posts.filter` to false to hide the row.
- Pick a post count that fills the rows: the newest drawn wide, then rows of three, like 7 or 10.
- Pass your router's link once in `renderLink`. The navbar, the posts, the closing band, and the footer all use it. From a server page, render the page from a client file, since a server page cannot pass a function.
- Set `embedded` when you show the page inside another page, like a gallery, so it has no second main.
Avoid it for
- Hundreds of posts on one page. Show the newest, and split the rest across pages.
- A tag on only one post. Each tag should narrow the list to a few.
Props
- navbar
The Navbar's props. It floats as a pill once the page scrolls, and folds into a menu below 42rem.
NavbarProps
- header
The headline at the top of the page. It renders as the page's one h1.
HeadlineProps
- posts
Required. Post Grid's props, like `posts`. `featured` and `filter` default to true here. Each post's title is an h2, right under the page's h1.
PostGridProps
- cta
The closing band above the footer.
CallToActionBandProps
- footer
The Footer's props.
FooterProps
- renderLink
Draws every link in the navbar, the 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. |
| header | HeadlineProps | - | The headline at the top of the page. It renders as the page's one h1. |
| posts | PostGridProps | - | Required. Post Grid's props, like `posts`. `featured` and `filter` default to true here. Each post's title is an h2, right under the page's h1. |
| 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 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 header renders the page's one h1, and each post's title is an h2 under it.
- Each part keeps its own accessibility, listed on its page.
Examples
A short blog index
<BlogPage
navbar={{ brand: "Workspace", links: [{ href: "/blog", label: "Blog", current: true }] }}
header={{ title: "The blog.", muted: "What we ship, and how we work." }}
posts={{
posts: [
{ href: "/blog/shared-folders", title: "Shared folders are here", tag: "Product", date: "2026-10-28" },
{ href: "/blog/plan-a-release", title: "How to plan a release", tag: "Guides", date: "2026-10-12" },
],
}}
footer={{
brand: "Workspace",
columns: [{ title: "Company", links: [{ href: "/blog", label: "Blog" }] }],
legal: "© 2026 Workspace",
}}
/>