Super-AI-Components

Browser Frame

Browser chrome around a screenshot, capture or demo.

https://vercel.com/geist/browser
Captured page

What it is

Browser chrome — window dots, back/forward/reload glyphs and an address bar — wrapped around content you supply. The chrome is a picture of a browser, not a working one: nothing in it is clickable, and the frame drives nothing.

Why it matters

An agent that browses has to show you what it saw, and a raw screenshot dropped into a results list reads as part of your own interface. The frame is the smallest device that says 'this came from somewhere else', which is what stops a captured page being mistaken for a product surface. Read from Vercel's Geist, where the same chrome does a marketing job; the job here is narrower and the accessibility rules follow from that difference.

Observed in: Vercel Geist — the Browser component, positioned there for landing pages, docs and changelog posts. · No reference-board sighting. Family Q carries no board evidence and does not claim any (see decisions.md D20).

Anatomy

  1. 1browser-frameThe outer frame: one border, one radius, one clipping boundary.
  2. 2browser-frame-chromeThe bar. Disappears entirely when there is no address and both dots and controls are off.
  3. 3browser-frame-dotsThree window dots, monochrome and aria-hidden.
  4. 4browser-frame-controlsBack, forward and reload glyphs. Decorative, and aria-hidden.
  5. 5browser-frame-addressThe URL. Announced once from a single node; the two visible halves that make truncation work are hidden.
  6. 6browser-frame-contentThe well. Ratio-locked, so nothing reflows when an image finishes decoding.

How to use it

Reach for it when the content inside came from outside your product — a browsing step, a computer-use capture, a fetched page rendered as a tool result. Do not reach for it to decorate your own UI: framing your product as a web page is a claim about provenance, and making that claim falsely is worse than a plain card. Pass the real URL if you have one; omit it if you do not, rather than passing a plausible-looking placeholder.

Do

  • Pass the URL the content actually came from — it is the record of where the agent went.

    https://vercel.com/geist/browser
  • Show the loading state at the same ratio the content will occupy, so the page does not jump when it resolves.

    https://vercel.com/geist/browser
  • Drop to dots alone when the source is obvious from context and the URL would only add noise.

Don't

  • Do not invent an address to make a frame look finished. Omit it instead — an empty bar is honest, a fabricated URL is not.

  • Do not use the inverted tone to mean anything. It is a surface for sitting against a light page, not a state.

    https://vercel.com/geist/browser

Accessibility

Keyboard
  • Zero tab stops. The address is a div and the controls are spans, so the component adds nothing to the tab order no matter which props are set.
  • There are no activation keys, because there is nothing to activate. If you need the frame to be clickable, wrap it in your own link or button — that wrapper owns the focus ring, and the frame will not compete with it.
  • The component has no disabled prop. A picture of a browser has no enabled state to remove.
Screen reader
  • The address is read as plain text, once. It sits in a single sr-only span; the two visible halves that produce the middle truncation both carry aria-hidden, because accname concatenates adjacent name-from-content chunks with no separator and would otherwise announce a mangled URL.
  • This is the deliberate divergence from Geist, which marks its whole chrome aria-hidden. That is right when the URL is illustrative marketing copy. Here it is data, and hiding it would leave a screen-reader user knowing a page was visited but not which one.
  • Dots and controls carry aria-hidden and announce as nothing. They depict browser affordances the component does not have, so announcing them would describe capabilities that are not there.
  • The frame contributes no role, name or landmark. Everything a screen reader gets from the content well comes from the child you pass — an image needs its own alt text, and an empty well announces as nothing at all.
  • The loading state has no live region. A frame that flips from skeleton to content announces nothing; if that transition matters, put the announcement on whatever owns the fetch.

Watch out for

  • Passing content that is not ratio-locked. The well locks its own aspect ratio, but a child sized in its own units will simply overflow and clip. Let the child fill the well (size-full, or object-cover on an image) rather than sizing it independently.
  • Treating the address bar as an input. It is not editable and never will be — making it one would put a text field in a component whose entire premise is that it drives nothing.
  • Assuming a long URL truncates from the end. It truncates in the middle: the host stays at the head and the final path segment is pinned. A path segment longer than 24 characters is not pinned at all, because pinning it would hold the string at full width and defeat the truncation.
  • Reaching for it as a generic card. The frame is a provenance claim. Once it wraps your own UI it stops meaning 'from elsewhere', and every honest use of it in the same product gets quieter.

Installation

npx shadcn@latest add https://super-ai-components.vercel.app/r/browser-frame.json

Requires a shadcn app on Base UI (style base-nova). Radix-based styles such as new-york fail to typecheck: these components use Base UI's render prop and callbacks like onOpenChangeComplete, which Radix primitives do not have.