Photographic Year Ledger

(How it grew)

The same system, photographed every two years.

See where it goes next

A brand is not finished when it launches. These are the four points where ours had to change and what forced each one.

2018

Minimal coffee bag standing against a plain wall

One shop, one sign

The first identity fitted on a single fascia and a paper bag. It did not need rules because there was only one of everything.

2020

Minimal coffee packaging arranged on a concrete surface

Packaging that had to travel

Wholesale meant the bag had to work on someone else's shelf, next to brands shouting louder. The type got heavier and the palette got quieter.

2023

Pared-back retail interior with monochrome shelving

Rooms in four cities

Interiors forced the first real system: a kit local fitters could apply without a designer in the room.

2026

Open print publication with a clean two-column layout

A catalogue and a second brand

Print brought back the parts we had simplified away, so the system now carries an editorial voice alongside the retail one.

About this block

Photographic Year LedgerPRO

Timeline where every ruled row lines up a year, a photograph from that moment and the explanation of what forced the change, headed by a pill CTA on the heading line and stacking to a readable block on mobile.

Feature254: Photographic Year Ledger

A timeline set as a ruled ledger: year, photograph and story on one row, repeated down the page. The CTA sits in the header beside the heading, so the sequence ends on the last entry.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/feature254?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/feature254?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/feature254.tsx plus the badge7 eyebrow and button12 pill button it uses.

Quick start

The installed file exports feature254Demo alongside the block: the exact props behind the preview above. Spread it to get a working section in one line.

tsx
import { Feature254, feature254Demo } from "@/components/beste/block/feature254";

export default function Page() {
  return <Feature254 {...feature254Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Feature254 } from "@/components/beste/block/feature254";

export default function Page() {
  return (
    <Feature254
      badge={{ label: "How it grew" }}
      heading="The same system, photographed every two years."
      description="A brand is not finished when it launches."
      entries={[
        {
          year: "2018",
          title: "One shop, one sign",
          description: "The first identity fitted on a single fascia and a paper bag.",
          src: "/history/2018.jpg",
          alt: "Coffee bag against a plain wall",
        },
        {
          year: "2020",
          title: "Packaging that had to travel",
          description: "Wholesale meant the bag had to work on someone else's shelf.",
          src: "/history/2020.jpg",
          alt: "Coffee packaging on concrete",
        },
      ]}
      button={{ label: "See where it goes next", href: "/studio" }}
    />
  );
}

Props

PropTypeDefaultDescription
badge{ label: string }–Eyebrow label rendered as a Badge7
headingstring–Section heading in the header row
descriptionstring–Supporting paragraph under the header
entriesTimelineEntry[][]Ledger rows, rendered top to bottom
buttonActionButton–Pill Button12 in the header, opposite the heading
classNamestring–Extra classes for the outer <section>
ts
type TimelineEntry = {
  year: string;
  title: string;
  description: string;
  src: string;
  alt: string;
};

type ActionButton = {
  label: string;
  href: string;
};

Behavior notes

  • Rows use md:grid-cols-[minmax(0,8rem)_minmax(0,20rem)_1fr], so the year column stays narrow, the photograph keeps a fixed maximum width and the story takes whatever is left. The ledger reads as columns, not as three equal thirds.
  • Years are set with tabular-nums, which keeps every date the same width and the column optically straight.
  • Every row opens on border-t py-8, so the sequence is ruled rather than boxed, and adding an entry never changes the rhythm.
  • Rows are md:items-start, so a short story sits level with the top of its photograph instead of floating in the middle of a tall row.
  • Below md the three parts stack in source order (year, picture, story), which is the reading order the ledger implies.
  • The CTA lives in the header on the heading's baseline, so the section can end on the most recent entry rather than on a button.
  • Photographs hold aspect-[4/3] at every width and carry no hover treatment, keeping the ledger a record rather than a gallery of links.

More Feature blocks

View all Feature
PRO

feature246

Switchable Capability Ledger

PRO

feature263

Day Timeline Rows

PRO

feature313

Timeline Rail

Get Pro access