Patterns
Info Tile
A tile with an icon, a label and a value for one fact about a record.
Patterns
A tile with an icon, a label and a value for one fact about a record.
import { InfoTile, StatusBadge } from 'ferry-ui'
import { CreditCard, Globe, ShieldCheck, User } from 'lucide-react'
export default function InfoTileHero() {
return (
<div className="grid gap-x-8 gap-y-6 sm:grid-cols-2">
<InfoTile icon={<ShieldCheck />} label="Status" value={<StatusBadge tone="success" label="Active" />} />
<InfoTile icon={<CreditCard />} label="Plan" value="Pro" hint="Renews on Nov 1, 2026" />
<InfoTile icon={<User />} label="Owner" value="Maya Chen" hint="maya@example.com" />
<InfoTile icon={<Globe />} label="Timezone" value="Europe/Paris" />
</div>
)
}Import the component. A tile has one part and no border.
import { InfoTile } from 'ferry-ui'
<InfoTile icon={<Icon />} label="" value="" hint="" />Put the tiles in a grid, for example grid gap-x-8 gap-y-6 sm:grid-cols-2.
hint adds a second line below the value.
import { InfoTile } from 'ferry-ui'
import { CalendarDays, CreditCard } from 'lucide-react'
export default function InfoTileHint() {
return (
<div className="grid gap-x-8 gap-y-6 sm:grid-cols-2">
<InfoTile icon={<CreditCard />} label="Plan" value="Pro" hint="8 of 10 seats used" />
<InfoTile icon={<CalendarDays />} label="Next invoice" value="Nov 1, 2026" />
</div>
)
}icon is optional. A tile with no icon shows only the text.
import { InfoTile } from 'ferry-ui'
export default function InfoTileNoIcon() {
return <InfoTile label="Billing email" value="billing@example.com" hint="The invoices go to this address." />
}value accepts an element. Use it for a Status, a link or a value with a Copy button.
import { CopyButton, InfoTile, StatusBadge } from 'ferry-ui'
import { Activity, Fingerprint } from 'lucide-react'
const WORKSPACE_ID = 'ws_8f3a21c9e04b'
export default function InfoTileValueElement() {
return (
<div className="grid gap-x-8 gap-y-6 sm:grid-cols-2">
<InfoTile
icon={<Activity />}
label="Status"
value={<StatusBadge tone="info" label="Syncing" pulse />}
hint="Started 2 minutes ago"
/>
<InfoTile
icon={<Fingerprint />}
label="Workspace ID"
value={
<span className="flex min-w-0 items-center gap-2">
<span className="truncate font-mono text-sm">{WORKSPACE_ID}</span>
<CopyButton value={WORKSPACE_ID} what="workspace ID" variant="ghost" />
</span>
}
/>
</div>
)
}The value and the hint stay on one line. The tile cuts a long text. Give the full text in a title attribute.
import { InfoTile } from 'ferry-ui'
import { Globe } from 'lucide-react'
const WEBSITE = 'www.acme-customer-portal.example.com'
export default function InfoTileLongValue() {
return (
<InfoTile
className="w-full max-w-xs"
icon={<Globe />}
label="Website"
value={<span title={WEBSITE}>{WEBSITE}</span>}
hint="This address shows on each invoice and in each email to a customer."
/>
)
}Set loading while the value loads. The tile shows a skeleton, hides the hint and sets aria-busy.
import * as React from 'react'
import { InfoTile, Label, Switch } from 'ferry-ui'
import { CalendarDays, User } from 'lucide-react'
export default function InfoTileLoading() {
// In an app, `loading` comes from the request that loads the record.
const [loading, setLoading] = React.useState(true)
return (
<div className="flex flex-col gap-5">
<div className="flex items-center gap-2">
<Switch id="tiles-loading" checked={loading} onCheckedChange={setLoading} />
<Label htmlFor="tiles-loading">Loading</Label>
</div>
<div className="grid gap-x-8 gap-y-6 sm:grid-cols-2">
<InfoTile icon={<User />} label="Owner" value="Maya Chen" hint="maya@example.com" loading={loading} />
<InfoTile icon={<CalendarDays />} label="Next invoice" value="Nov 1, 2026" loading={loading} />
</div>
</div>
)
}InfoTile also accepts each attribute of the <div> element.
| Prop | Type | Default |
|---|---|---|
labelRequired | ReactNode | - |
Short caption rendered as a | ||
icon | ReactNode | - |
Icon shown in an | ||
value | ReactNode | - |
Main value. Plain text, or a small inline node (badge, link, value + copy button). Truncated to one line: add a | ||
hint | ReactNode | - |
Optional secondary line under the value ("Renews on May 4", "of 10 seats"). Truncated to one line. | ||
loading | boolean | false |
Shows a skeleton instead of the value, hides the hint and sets | ||