Pattern gallery

File chip

A filename as a quiet clickable token: mid-truncated so the extension survives, opening a preview in place.

What this solves

Visual and self-explaining action container. Mid-truncation keeps the start and the extension so two long exports from the same vendor still read apart, and the small ↗ glyph tells a reader the chip opens something before they click it.
Use it when
  • A file attached to a job or a scrape run.Speedway's FileChip (file-chip.tsx:33-72), opening a shared preview host.
  • A schema or export file named inline in a table cell.Any row where a filename doubles as a naming label rather than a value.

Rendered

Fixture data; check both themes.
Clickable, opens a preview
Plain, nothing to open
2026-08-light…ll-export.xml
Click a file chip to open its preview.
patterns/file-chip/example.tsxCopy this into a fresh route and it renders as above.
"use client";
import { useState } from "react";
import { FieldChip, RenderIcon } from "@versable-git/ui";
// Mid-truncation keeps the start and the extension; the middle elides, so
// two long exports from the same vendor still read differently. Clicking a
// clickable chip opens it below, standing in for the real shared preview host.
function midTruncate(name: string, max = 28): string {
if (name.length <= max) return name;
const keep = Math.floor((max - 1) / 2);
return `${name.slice(0, keep)}…${name.slice(-keep)}`;
}
function FileChip({
filename,
clickable,
opened,
onOpen,
}: {
filename: string;
clickable?: boolean;
opened?: boolean;
onOpen?: (name: string) => void;
}) {
return (
<FieldChip
tone="file"
onClick={clickable ? () => onOpen?.(filename) : undefined}
className={
opened
? "bg-warning/15 text-warning ring-warning/25 w-fit max-w-full whitespace-nowrap"
: "bg-base-200 text-base-content/70 ring-base-300 w-fit max-w-full whitespace-nowrap"
}
>
<span className="inline-flex items-center gap-1">
<RenderIcon Icon="FileSheet" size={12} className="shrink-0" />
<span className="font-mono text-xs">{midTruncate(filename)}</span>
{clickable ? <span className="text-base-content/65 text-[10px]"></span> : null}
</span>
</FieldChip>
);
}
export function FileChips() {
const [opened, setOpened] = useState<string | null>(null);
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-2">
<span className="text-base-content/45 text-xs font-medium tracking-wide uppercase">Clickable, opens a preview</span>
<div className="flex flex-wrap gap-2">
<FileChip
clickable
filename="brake-pads-q3-vendor-export.xlsx"
opened={opened === "brake-pads-q3-vendor-export.xlsx"}
onOpen={setOpened}
/>
<FileChip clickable filename="fitment.csv" opened={opened === "fitment.csv"} onOpen={setOpened} />
</div>
</div>
<div className="flex flex-col gap-2">
<span className="text-base-content/45 text-xs font-medium tracking-wide uppercase">Plain, nothing to open</span>
<div className="flex flex-wrap gap-2">
<FileChip filename="2026-08-lighting-catalog-full-export.xml" />
</div>
</div>
<div className="text-base-content/65 text-xs">
{opened != null ? (
<>
Preview open: <span className="font-mono">{opened}</span>
</>
) : (
"Click a file chip to open its preview."
)}
</div>
</div>
);
}

Where it ships

  • speedway/app/components/file-chip.tsx:33-72FieldChip tone=file with a file-sheet icon, mid-truncated name, and an external-link glyph; quiet gray by owner ruling
  • speedway/app/components/file-chip.tsx:77-80the mid-truncation helper: keep the start and the extension, elide the middle

App-specific: The real chip opens a shared preview host through a hook (one host mounted in the shell, many chip triggers), and degrades to a plain chip when there is no source id to open. Clicking never navigates.

@versable-git/ui · composites proven in the apps