Docs menu

Components · · 3 min read

UI components: BrowserFrame, SystemFlow & Stat — with live examples

Three server-first components used across this site — a browser mockup, a tag-based architecture diagram, and a count-up number — rendered live on this page, with full source code.

Hono JSXTailwind CSSVanilla JSA11y
Live exampleRendered by the actual component — not an image.
alifnugraha.my.id/preview
0/7

AI coverage

<0s

Load on 4G

These three small components appear on nearly every page of this site: BrowserFrame (a browser chrome for mockups), SystemFlow (a system-flow diagram), and Stat (a number that counts up when visible). The live example is rendered right above ↑ — not an image. Here’s the source code and the design decisions behind it.

BrowserFrame — a mockup without screenshots

The problem: showing “product” before real screenshots exist. The solution is a minimal browser chrome — three dots, an address bar, then a children slot for any content:

export const BrowserFrame = ({ url, children }: { url: string; children?: Child }) => (
  <div class="card overflow-hidden">
    <div class="flex items-center gap-3 border-b border-line bg-surface px-4 py-2.5">
      <div class="flex gap-1.5" aria-hidden="true">
        <span class="h-2 w-2 rounded-full border border-line bg-base"></span>
        <span class="h-2 w-2 rounded-full border border-line bg-base"></span>
        <span class="h-2 w-2 rounded-full border border-line bg-base"></span>
      </div>
      <div class="min-w-0 flex-1 truncate rounded-sm border border-line bg-base px-3 py-1
                  font-mono text-[10px] tracking-[0.06em] text-muted">{url}</div>
    </div>
    <div class="bg-base">{children}</div>
  </div>
);

The detail that matters: min-w-0 + truncate on the address bar. Without it, a long URL forces the content’s minimum width past a phone’s viewport — the page “shrinks” to the left (a real bug this site once had).

SystemFlow — an architecture diagram made of tags

A flow diagram (Storefront → Checkout → AI chat → RAG → Ops) without a diagram library: just a row of tags with animated “beams” between them. On mobile the beams hide and the tags are allowed to wrap:

export const SystemFlow = ({ nodes }: { nodes: string[] }) => (
  <div class="flex flex-wrap items-center justify-center gap-2 py-1 md:flex-nowrap md:gap-0"
       role="img" aria-label="System flow diagram">
    {nodes.map((node, i) => (
      <>
        {i > 0 && <span class="beam hidden w-10 shrink-0 md:block" aria-hidden="true"></span>}
        <span class="tag shrink-0 bg-base">{node}</span>
      </>
    ))}
  </div>
);

role="img" + aria-label makes screen readers announce one diagram instead of five random tags.

Stat — the count-up number

The server renders the final value in a data-count attribute (SEO and no-JS users still get the number — the initial prefix + 0 + suffix text is only animated when JS is on). Vanilla JS in site.js animates it when the element enters the viewport via IntersectionObserver:

export const Stat = ({ prefix, count, suffix, label, delay }: StatProps) => (
  <div class="text-center" data-reveal style={"--d: " + delay}>
    <div class="font-display text-4xl font-semibold tracking-tight md:text-5xl">
      <span data-count={count} data-prefix={prefix} data-suffix={suffix}>{prefix}0{suffix}</span>
    </div>
    <p class="mt-2 font-mono text-[11px] uppercase tracking-[0.18em] text-muted">{label}</p>
  </div>
);

The principle behind all three

  • Server-first: all content exists in the initial HTML; JavaScript only adds motion.
  • Zero dependencies: no carousel/diagram/count-up library — the site’s total JS stays under 20KB.
  • Mobile enforced with classes, not JS: flex-wrap, hidden md:block, min-w-0 — layout never depends on runtime measurement.

Want something like this built for your business?