Super-AI-Components

Asset Detail

Lightbox: media + provenance rail

What it is

The lightbox for a single result: the media at full size beside a provenance rail carrying the prompt, the parameters that produced it, the cost, and the three handoff verbs — Copy prompt, Remix, Edit. Phrases within the prompt can be marked selectable; clicking one reports its exact text through `onSpanSelect`, and it is the host that decides what to do with it — `onRemix` fires with the whole prompt, not the phrase.

Why it matters

Opening a result full-size is almost never about looking at it. It is about doing it again, slightly differently — which means the prompt is editable material, not a caption underneath the picture. Highlighting the phrases worth changing and feeding a clicked phrase straight into Remix is what turns a viewer into a starting point. The parameters matter for the same reason but a stricter one: seed and sampler are what make a result reproducible at all, so they render through A10 `stat-readout` — the same grid N5 `run-inspector` uses, down to its em-dash for a value that is missing — and they are copyable. A lightbox that shows a beautiful image and none of what made it is a dead end.

Observed in: Midjourney · Playground · Freepik

Anatomy

  1. 1asset-detailThe dialog surface.
  2. 2asset-detail-mediaThe result at full size. Opaque — any media works.
  3. 3asset-detail-railThe provenance column beside the media.
  4. 4asset-detail-promptThe prompt, segmented into plain and selectable runs.
  5. 5asset-detail-spanOne highlighted phrase. A button when selectable, a mark when not.
  6. 6asset-detail-verbsCopy prompt · Remix · Edit, always in that order.
  7. 7stat-readoutA10, rendering the params grid — the same one N5 uses.
  8. 8asset-detail-costWhat this result cost, via the shared formatter.
  9. 9asset-detail-moreMore-like-this suggestions, supplied by you.

How to use it

Control `open` yourself and pass `media` as whatever the result actually is — the component treats it as opaque. Give `prompt` the full text and `highlightedSpans` the character ranges worth reusing; wire `onSpanSelect` and it will hand you the exact substring plus its range, ready to seed a remix. Pass `params` in A10's shape and mark seed and sampler `copyable`. All three verbs always render; supplying a handler is what enables one, so a dialog with no `onRemix` still shows Remix, greyed.

Do

  • Highlight the phrases someone would actually want to change, and wire onSpanSelect so a click starts a remix.

  • Include seed and sampler, and mark them copyable — they are what make the result reproducible.

Don't

  • Don't treat the prompt as a caption; without onSpanSelect and onRemix the dialog is a dead end.

  • Don't ship provenance without a seed — the result can be admired but never reproduced.

Accessibility

Keyboard
  • The dialog is modal, so every tab stop is inside it and Escape closes it. The cycle is: one button per selectable span, Copy prompt, Remix, Edit, one copy button per `copyable` parameter that has a value, anything interactive you passed in `moreLikeThis`, then the close X.
  • A verb with no handler is rendered `disabled`, which takes it out of the tab order entirely. A dialog wired only for `onRemix` is one reachable verb, not three, and nothing in the dialog explains the other two.
  • A highlighted span is a tab stop only when `onSpanSelect` is passed. Without it the same range renders as a `<mark>`: still highlighted, not focusable, not activatable — which is how a highlight that looks interactive ends up not being.
  • The spans are ordinary buttons in prose, so a prompt with six selectable phrases is six tab stops before Copy prompt. There is no arrow-key traversal of the prompt and no way to skip past it.
  • The parameter labels, the cost line and the media are text; only the copy buttons in the params grid are reachable. There is no keyboard route to the image itself.
Screen reader
  • The dialog is always named "Result detail", from an `sr-only` title with no prop behind it. Every asset's lightbox therefore announces identically — the name never says which result is open, and there is no way to override it from the call site.
  • Its description is fixed too: "The generated result at full size, with the prompt and parameters that produced it." It describes the component, not this result.
  • Each selectable span's accessible name is its own text, so what a reader hears is exactly the substring `onSpanSelect` will hand back. The trade-off is that the prompt is announced as a run of alternating text and buttons rather than as one sentence.
  • The three verb icons are `aria-hidden`; the names are the visible words — "Copy prompt", "Remix", "Edit".
  • Every copyable parameter's copy button is labelled just "Copy". A10 hard-codes that name and hides its `⧉` glyph, so marking seed and sampler `copyable` produces two buttons a reader cannot tell apart; only the `<dl>` reading order ties one to its `<dt>`.
  • A parameter with no value renders A10's em-dash, which is announced as the character rather than as "unknown". The absence is deliberate but it is not spoken as deliberate.
  • Nothing announces a copy. Neither this dialog nor A10 owns a live region, so pressing Copy prompt or a parameter's copy button is silent — put the confirmation somewhere yourself if it matters.
  • `media` and `moreLikeThis` are opaque slots whose accessible names are entirely yours. An `<img>` with no `alt` in `media` leaves the largest thing in the dialog unannounced, and the fixed title will not cover for it.
Focus
  • Opening moves focus into the popup and marks the rest of the page inert; closing restores focus to whatever was focused before, usually the tile that opened it. That is the primitive's default and needs nothing at the call site.
  • Focus lands on the first tabbable element inside, which is the first highlighted span when the prompt has one and Copy prompt otherwise — never the heading, because the heading is `sr-only`.
  • Spans and A10's copy buttons carry their own `focus-visible:ring-2`; the three verbs and the close X use the shared button ring. Every control here is visible on focus without a global style.

Watch out for

  • highlightedSpans are character offsets into prompt, so they go stale the moment the prompt is reworded. Derive them (indexOf, or from whatever produced them) rather than hard-coding numbers.
  • Overlapping, reversed and out-of-range spans are dropped or clamped rather than throwing, and the full prompt always renders exactly once. That means a bad range fails silently — if a highlight does not appear, check the offsets.
  • A span with no onSpanSelect renders as a <mark> instead of a button. That is intentional (the highlight is still information) but it also means forgetting the handler produces something that looks interactive and is not.
  • The verbs are always present and disable themselves without handlers, so the set of three reads the same everywhere. If you need a genuinely different action set, that is F4 action-stack, not this.
  • params is A10's shape verbatim, which is what keeps this grid and N5 run-inspector identical. Reshaping it here would fork provenance rendering across the registry.

Installation

npx shadcn@latest add https://super-ai-components.vercel.app/r/asset-detail.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.