Skip to content
txbrendan/ui

Article

beta

The reading layout of one blog post: a back link, tag, title, lead, author, date, and cover, then the post as plain HTML, with a list of its sections on wide screens.

Built from Avatar, Badge, Button, and Glide Highlight.

Preview

All posts
Guides6 min read

How to plan a release

The steps a team can follow from the first list of changes to the day it ships.

Priya RamanProduct lead

A release goes better when everyone knows what is in it, who owns each part, and when it ships. These are the four 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 link it to its ticket, so anyone can see the detail.

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

  1. The last change is merged.
  2. Testing starts on the release branch.
  3. The release notes are written.
  4. The release ships.

When a date slips

Keep the days for testing, and move the release day instead. Say the new date in the same place you said the first one.

Move the release, not the testing.

Release checklist

Check the build

Run the checks on the release branch with npm run check. Every check must pass before the build goes out.

git switch -c release/2.4
npm ci
npm run check
npm run build
The release plan, one dot for each change.

Share the release notes

Post the notes where people already look, like the changelog and the team channel. Link each line to its docs, and name who to ask about it.

title, date, and body only

Notes from the October planning day

We met for one day to plan the next quarter. These notes cover what we agreed, and what we left for later. Ask in the team channel if a line is not clear.

The next planning day is in January. The date goes out a month before.

narrow container

All posts
Guides6 min read

How to plan a release

The steps a team can follow from the first list of changes to the day it ships.

Priya RamanProduct lead

Start with a short list of what the release changes. Keep each line to one change.

git switch -c release/2.4 && npm ci && npm run check && npm run build

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the website component. It also adds every component it uses. Its own files go in your components folder, and they are yours to edit.

    npx shadcn@latest add @txbrendan/article

Use it for

  • The page for one blog post, guide, or announcement, under the site's Navbar.
  • Pass the post as children, as plain HTML or the output of your Markdown: h2, h3, p, a, ul, ol, blockquote, code, pre, figure, img, and hr. The Article styles them.
  • Give each section an h2. With two or more, a list of them sits beside the body on wide screens, and marks the one being read.
  • Pass `back` to link to the list of posts, and your router's link through `renderLink`, like next/link.
  • Pass `date` as a plain date, like "2026-10-12". It prints the same on the server and in the browser.
  • Pass `cover` as your own image or a drawing. It fills a 16:9 frame at every width, and an image is cropped to fit.
  • The title and section headings are set in the theme's `font-headline`. Load Geist Serif as `--font-geist-serif`, or it falls back to the system serif.
  • A pinned navbar covers the top of the page. Set `--article-offset` on the Article to its height plus a gap, like className="[--article-offset:5rem]". It sets where the list sticks and where a jump to a section lands. The default is 6rem.

Avoid it for

  • A list of posts. Use a grid of cards that link here.
  • Docs with a sidebar of pages. The sections list covers one page only.
  • A Hero above it. The title is the page's h1.
  • Headings in the body other than h2 and h3. Use h2 for each section and h3 inside one.

Props

  • title

    The post's title. It is the page's h1, in the serif.

    ReactNode

  • lead

    One or two sentences under the title that say what the post covers.

    ReactNode

  • tag

    A short tag over the title, like "Guides".

    ReactNode

  • back

    The link back to the list of posts, like { href: "/blog", label: "All posts" }.

    { href: string; label: string }

  • author

    Who wrote it. The name gives the avatar its initials and tint, and avatar is a photo URL.

    { name: string; role?: ReactNode; avatar?: string }

  • date

    When it was published, as a date like "2026-10-12". A string that is not a date shows as it is.

    string

  • readingTime

    How long it takes to read, like "6 min read".

    ReactNode

  • cover

    A picture under the header, like an <img> or a drawing, in a 16:9 frame. It spans the body and the sections list.

    ReactNode

  • children

    The post itself, as plain HTML.

    ReactNode

  • renderLink

    Draws the back link. Spread the props onto your router's link: (props) => <Link {...props} />. The renderLink a site passes its Navbar fits too.

    (props: ArticleLinkProps) => ReactNode · default a plain <a>

  • locale

    Formats the date. Fixed, so the server and the browser print the same words.

    string · default "en-US"

  • contentsLabel

    The heading over the list of sections, on wide screens.

    string · default "On this page"

  • className

    Extra classes on the article, which holds the container query.

    string

Accessibility

  • The root is an article. The title is its h1, so the page should have no other.
  • The date is a time element with the date you passed as its datetime.
  • The author's name is written out, so screen readers skip the avatar.
  • The sections list is a nav named by its heading. The link for the section being read has aria-current="location".
  • A press on a section scrolls to it, smoothly unless reduced motion is on, updates the address, and moves focus to its heading, so Tab carries on from there.
  • Copy link keeps its name. Screen readers hear "Link copied", or that it could not copy, from a polite live region.
  • A code block that is wider than the column scrolls inside itself, and takes focus so keys can scroll it.
  • Links in the body are underlined, so they never rely on color alone.

Examples

A blog post

<Article
  back={{ href: "/blog", label: "All posts" }}
  tag="Guides"
  date="2026-10-12"
  readingTime="6 min read"
  title="How to plan a release"
  lead="The steps a team can follow from the first list of changes to the day it ships."
  author={{ name: "Priya Raman", role: "Product lead" }}
>
  <h2>Write down the scope</h2>
  <p>Start with a short list of what the release changes.</p>
  <h2>Set the dates</h2>
  <p>Pick the day it ships first, then work back from it.</p>
</Article>

With next/link, a cover, and another locale

<Article
  title="Planifier une version"
  date="2026-10-12"
  locale="fr-FR"
  contentsLabel="Sur cette page"
  back={{ href: "/blog", label: "Tous les articles" }}
  cover={<img src="/blog/release.png" alt="" width={1600} height={800} />}
  renderLink={(props) => <Link {...props} />}
>
  <p>Commencez par une courte liste.</p>
</Article>