components / salon

salon.

a salon hang: a wall of images shown at their own aspect ratios, packed into justified rows that each fill the width (the standard greedy justified-gallery algorithm, with a sparse trailing row left-aligned at the target height instead of ballooned into a billboard). it isn't `gallery` (the uniform searchable card grid — every project the same card sized to a column), `shelf` (one horizontal scrolling row you skim), or `showcase` (a single framed preview): a salon keeps every piece's real proportions and makes the varied hang itself the point, so reach for it when the images are the content and their shapes carry meaning. the row-packing geometry ships as a separate tested module.

wide bannerthe horizon line
tall postermonolith
square studystudy in gray
16:9 stillwide angle
portraitportrait no. 4
square studysecond study
4:3 landscapestill life
small squareswatch

every piece keeps its own proportions; each row is justified to fill the width. narrow the window and the hang reflows. two pieces link out.

installation
bunx @justin06lee/chrome@latest add salon
props
nametypedefaultdescription
items *SalonItem[]-the pieces to hang, each at its own aspect ratio: { src?: string; width: number; height: number; href?: string; alt?: string; title?: ReactNode; external?: boolean }[]. width/height are intrinsic pixels that set the aspect ratio, not the render size; omit src to render a typographic placard in the slot; a title shows as a placard on hover/focus; external (or an http(s) href) opens in a new tab as a plain <a>.
targetRowHeightnumber260ideal row height in px before a row is justified to fill the width.
gapnumber12gap between pieces and rows in px.
maxRowHeightnumbertargetRowHeight * 1.5how tall a trailing, un-fillable row may grow before it stops stretching and left-aligns at the target height.
maxWidthnumber-container max width in px. unbounded by default (fills its parent).
assumedWidthnumber1040assumed width for the first (server) render, before the container is measured.
linkComponentReact.ElementType-router link for internal hrefs (e.g. next/link). defaults to a plain <a>; external hrefs always stay <a>.
ariaLabelstring-accessible name for the wall region.
classNamestring-