Article With Sticky Contents Rail

Practice

The estimate is the design work nobody sees

Pricing a project is not admin you do after the thinking. It is the first place the thinking becomes visible, and the place most studios give theirs away for free.

Maria Santos

Maria Santos, Partner

18 March 2026 · 9 min read

A printed estimate and notes spread across a desk

Every studio we know treats the estimate as the boring part. The proposal gets designed, the case studies get rewritten, and the number arrives in a table at the end as though it fell out of a calculator.

What a number is actually saying

A price is a description of the work in one line. It says how long you think the problem is, how much of it you have solved before, and how much room you are leaving for the parts nobody has thought of yet.

  • How long the problem is, in weeks you are willing to name out loud.
  • How much of it you have already solved on someone else's project.
  • How much room you are keeping for the part nobody has thought of yet.

The four lines we always show

We break every estimate into the same four lines, no matter the size of the project. Not because clients ask for it, but because writing them forces us to admit which line we are guessing at.

Discovery is a line, not a favour

The two weeks spent reading, interviewing and arguing before anything is drawn are the two weeks that decide the rest. Giving them away signals they were never worth much.

If the first line of your estimate is free, you have already told the client which part of the work you do not value.

What we stopped doing

We stopped sending ranges. A range is a way of asking the client to pick a number for us, and they always pick the lower one, then remember it as the price.

We also stopped apologising in the covering note. The number is the number. If it is wrong for them, that is a useful thing to find out in week one rather than week nine.

About this block

Article With Sticky Contents RailPRO

A full article layout with a ruled byline row, a wide lead image, prose blocks that carry their own anchors, an accent-ruled pull quote, a closing tag row, and a contents rail that sticks beside the text on desktop.

Post88: Article With Sticky Contents Rail

A full article layout with a ruled byline row, a wide lead image, prose blocks that carry their own anchors, an accent-ruled pull quote, a closing tag row, and a contents rail that sticks beside the text on desktop.

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/post88?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

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

This installs the block to components/beste/block/post88.tsx and the badge6 component it uses for the eyebrow (installed to components/beste/component/badge6.tsx).

Quick start

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

tsx
import { Post88, post88Demo } from "@/components/beste/block/post88";

export default function ArticlePage() {
  return <Post88 {...post88Demo} />;
}

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

tsx
import { Post88 } from "@/components/beste/block/post88";

export default function ArticlePage() {
  return (
    <Post88
      eyebrow="Practice"
      title="The estimate is the design work nobody sees"
      summary="Pricing a project is not admin you do after the thinking."
      date="18 March 2026"
      readTime="9 min read"
      author={{
        name: "Maria Santos",
        title: "Partner",
        avatar: { src: "/people/maria.jpg", alt: "Maria Santos" },
      }}
      featuredImage={{ src: "/covers/estimate.jpg", alt: "An estimate on a desk" }}
      tocTitle="On this page"
      content={[
        { type: "paragraph", content: "Every studio we know treats the estimate as the boring part." },
        {
          type: "heading",
          content: "What a number is actually saying",
          anchor: { title: "What a number is saying", id: "what-a-number-is-saying" },
        },
        { type: "list", items: ["How long the problem is.", "How much you have solved before."] },
        { type: "quote", content: "If the first line of your estimate is free, you have already told the client which part you do not value." },
      ]}
      tags={[{ label: "Pricing", href: "/subjects/pricing" }]}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstring–Badge6 label above the article title
titlestring–Article title, rendered as the page h1
summarystring–Standfirst under the title
datestring–Publication date in the byline row
readTimestring–Reading time, printed after the date
authorAuthor–Name, role and portrait in the byline row
featuredImageFeaturedImage–Wide lead image under the byline
tocTitlestring–Label above the contents rail
contentContentBlock[][]The article body, block by block
tagsTag[][]Subject links under the article
classNamestring–Extra classes for the outer section
ts
type ContentBlock = {
  type: "paragraph" | "heading" | "subheading" | "list" | "quote";
  content?: string;
  items?: string[];
  anchor?: { title: string; id: string };
};

type Author = {
  name: string;
  title?: string;
  avatar?: { src: string; alt: string };
};

type FeaturedImage = {
  src: string;
  alt: string;
};

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

Behavior notes

  • The contents rail is derived from the content, not passed in. Any block carrying an anchor becomes an entry, and a heading without one simply does not appear in the rail.
  • Rail links call preventDefault and scroll with scrollIntoView({ behavior: "smooth" }), and the headings carry scroll-mt-24 so a sticky site header does not land on the title.
  • The rail is lg:sticky lg:top-24 beside the text on desktop and order-first above it on smaller screens, where a sticky sidebar would be in the way.
  • list blocks render as a real ul with accent markers, and quote blocks as a blockquote on an accent rule. Anything else falls through to a paragraph.
  • The article is a article element with a single h1, so a page using this block should not carry another one.

More Post blocks

View all Post
PRO

post87

Editorial Article Layout

PRO

post89

Long Read Article

PRO

post9

Article with Right Author Box

Get Pro access