Gallery shell - editorial swiss
shell-editorial.tsx
shellskill: emil-design-enggalleryeditorialtypographic
Variants of gallery-shell: shell-brutalist, shell-contact-sheet
Brief
The prompt that produced this. Re-run it to generate more in the same taste.
- Aesthetic
- editorial swiss - restrained, typographic, high-contrast, hairline rules, monospace metadata, generous whitespace, single accent
- Reference
- https://antitype.studio/
- Intent
- the library should read as a printed type specimen, where the work is the content and the chrome is just typography
- Guardrails
- always one accent color, hairline 1px rules, monospace for all metadatanever drop shadows, rounded corners above 2px, filled buttons
Install
Shells are library-internal - they take the registry as props and are not published to the registry.
Source
Meta block and library type import stripped - this is what pastes cleanly elsewhere.
import { Link } from '@tanstack/react-router'
import type React from 'react'
import { useMemo, useState } from 'react'
import { ScaledPreview } from '@/components/preview-frame'
const ACCENT = '#e4442b'
export default function ShellEditorial({ designs }: ShellProps) {
const [query, setQuery] = useState('')
const [activeTag, setActiveTag] = useState<string | null>(null)
const tags = useMemo(() => [...new Set(designs.flatMap((d) => d.tags))].sort(), [designs])
const visible = useMemo(() => {
const q = query.trim().toLowerCase()
return designs.filter((d) => {
if (activeTag && !d.tags.includes(activeTag)) return false
if (!q) return true
return `${d.title} ${d.id} ${d.skill ?? ''} ${d.tags.join(' ')}`.toLowerCase().includes(q)
})
}, [designs, query, activeTag])
return (
<div className="mx-auto max-w-[1100px] px-8 py-16">
<header className="border-foreground border-b pb-6">
<p className="font-mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground">
Index of generated work
</p>
<h1 className="mt-4 font-semibold text-6xl leading-[0.95] tracking-tighter">
Design
<br />
Library
</h1>
<div className="mt-6 flex items-baseline justify-between font-mono text-[11px] uppercase tracking-[0.15em]">
<span className="text-muted-foreground">
{visible.length} / {designs.length} entries
</span>
<span style={{ color: ACCENT }}>2026</span>
</div>
</header>
<div className="flex flex-wrap items-center gap-x-8 gap-y-4 border-b py-4">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search"
className="min-w-[12rem] flex-1 border-0 bg-transparent font-mono text-sm outline-none placeholder:text-muted-foreground"
/>
<div className="flex flex-wrap gap-x-4 gap-y-1 font-mono text-[11px] uppercase tracking-[0.15em]">
<button
type="button"
onClick={() => setActiveTag(null)}
className="pressable hover:opacity-60"
style={{ color: activeTag === null ? ACCENT : undefined }}
>
All
</button>
{tags.map((tag) => (
<button
key={tag}
type="button"
onClick={() => setActiveTag(activeTag === tag ? null : tag)}
className="pressable text-muted-foreground hover:opacity-60"
style={{ color: activeTag === tag ? ACCENT : undefined }}
>
{tag}
</button>
))}
</div>
</div>
{visible.length === 0 && (
<p className="py-24 text-center font-mono text-muted-foreground text-sm">
No entries match.
</p>
)}
<ol className="grid gap-x-10 gap-y-16 pt-16 md:grid-cols-2">
{visible.map((design, i) => (
// Stagger is capped at 8 steps: past that the last card would wait
// half a second, and a grid that takes 500ms to appear reads as slow.
<li
key={design.id}
className="stagger-in"
style={{ '--i': Math.min(i, 8) } as React.CSSProperties}
>
<Link to="/d/$id" params={{ id: design.id }} className="group block">
<div className="flex items-baseline justify-between font-mono text-[11px] uppercase tracking-[0.15em]">
<span style={{ color: ACCENT }}>{String(i + 1).padStart(2, '0')}</span>
<span className="text-muted-foreground">{design.kind}</span>
</div>
<div className="mt-3 border border-foreground/15 transition-[border-color] duration-200 ease-[var(--ease-out-strong)] group-hover:border-foreground/40">
<ScaledPreview
id={design.id}
designWidth={1280}
designHeight={860}
className="h-[280px]"
>
<design.Component designs={designs} />
</ScaledPreview>
</div>
<h2 className="mt-4 font-medium text-xl tracking-tight">{design.title}</h2>
<dl className="mt-2 space-y-1 font-mono text-[11px] text-muted-foreground uppercase tracking-[0.12em]">
<div className="flex gap-3">
<dt className="w-16 shrink-0">File</dt>
<dd>{design.id}.tsx</dd>
</div>
{design.skill && (
<div className="flex gap-3">
<dt className="w-16 shrink-0">Skill</dt>
<dd>{design.skill}</dd>
</div>
)}
{design.variantOf && (
<div className="flex gap-3">
<dt className="w-16 shrink-0">Set</dt>
<dd style={{ color: ACCENT }}>{design.variantOf}</dd>
</div>
)}
{design.tags.length > 0 && (
<div className="flex gap-3">
<dt className="w-16 shrink-0">Tags</dt>
{/* Spacing separates these, not middle dots. A metadata
line strung together with `ยท` is an AI signature. */}
<dd className="flex flex-wrap gap-x-3">
{design.tags.map((tag) => (
<span key={tag}>{tag}</span>
))}
</dd>
</div>
)}
</dl>
</Link>
</li>
))}
</ol>
</div>
)
}