Monogram tile
A neutral letter tile for companies and providers, never a brand logo.
| Supplier | Open invoices | Balance |
|---|---|---|
| Northwind Freight | 14 | $184,220.00 |
| Halcyon Logistics | 6 | $91,377.12 |
| Orchard Street Produce | 9 | $12,904.50 |
| Baker & Lowe Supply | 2 | $4,120.00 |
import { MonogramTile } from "@oration/canon/components/monogram-tile";export function Hero() { const suppliers = [ { name: "Northwind Freight", color: "blue", open: "$184,220.00", invoices: 14, }, { name: "Halcyon Logistics", color: "teal", open: "$91,377.12", invoices: 6, }, { name: "Orchard Street Produce", color: "green", open: "$12,904.50", invoices: 9, }, { name: "Baker & Lowe Supply", color: "amber", open: "$4,120.00", invoices: 2, }, ] as const; return ( <div className="w-full max-w-lg overflow-hidden rounded-xl bg-card shadow-border"> <table className="w-full text-13"> <caption className="sr-only"> Suppliers with open invoices </caption> <thead> <tr className="h-8 border-b border-border text-left text-muted-foreground"> <th scope="col" className="px-3 font-medium"> Supplier </th> <th scope="col" className="px-3 text-right font-medium"> Open invoices </th> <th scope="col" className="px-3 text-right font-medium"> Balance </th> </tr> </thead> <tbody> {suppliers.map((supplier) => ( <tr key={supplier.name} className="h-9 border-b border-border last:border-b-0" > <td className="px-3"> <span className="flex items-center gap-2"> <MonogramTile name={supplier.name} color={supplier.color} size="sm" /> <span className="truncate font-medium text-foreground"> {supplier.name} </span> </span> </td> <td className="px-3 text-right tabular-nums"> {supplier.invoices} </td> <td className="px-3 text-right font-medium tabular-nums"> {supplier.open} </td> </tr> ))} </tbody> </table> </div> );}Usage#
Monogram tile is a company's initials in a rounded square on a pale tag tint: suppliers, customers' companies, integrations, SSO and AI providers, teams and workspaces. It is Oration's stand-in for a logo, and it stays neutral on purpose: provider names such as Deepgram or Twilio appear as letter tiles, never as their brand marks. The square shape is the point. Circles are people, tiles are organizations, so keep the two apart.
When to use
- Beside a company or supplier name in rows, cells, cards and record headers: Northwind Freight, Halcyon Logistics.
- For integrations and AI or telephony providers in settings and pickers, in
grayso no provider looks endorsed. - For teams and a workspace without an uploaded logo, in the record's own tag color.
- At
xsinline in running text, such as an approval that names the supplier.
When not to use
- For a person, even one who represents a company. Use Avatar
- For an AI agent. Use Agent avatar
- For records with no meaningful letters, such as cohorts or anonymous callers. Use Identicon
- For a category or value of a record, such as a supplier's tier. Use Tag
The Option Hue Rule
No brand logos
Anatomy#
- Tile. A square from 16 to 48px with corners that step with size: 4, 5, 8, 10 and 12px.
- Tint. The tag fill for
color(--tag-{color}), with an inset 1px outline of black at 5% (white at 10% in dark) so pale tiles keep their edge. - Initials. Semibold, tight tracking, in the tag's deep ink. Two letters from
mdup, one atxsandsm.
Examples#
Sizes
16 to 48px. xs sits inline in text, sm and md in rows and cells, lg in cards and xl in record and settings headers. The two smallest show one letter.
import { MonogramTile } from "@oration/canon/components/monogram-tile";export function Sizes() { const sizes = [ { size: "xs", label: "xs, 16px" }, { size: "sm", label: "sm, 20px" }, { size: "md", label: "md, 24px" }, { size: "lg", label: "lg, 32px" }, { size: "xl", label: "xl, 48px" }, ] as const; return ( <div className="flex flex-wrap items-end justify-center gap-8"> {sizes.map((item) => ( <div key={item.size} className="flex flex-col items-center gap-2" > <MonogramTile name="Halcyon Logistics" color="teal" size={item.size} /> <span className="text-xs text-muted-foreground"> {item.label} </span> </div> ))} </div> );}Colors
Any of the ten tag colors, taken from the record. The color identifies the company and carries no other meaning.
- Twilio
gray - Northwind Freight
blue - Cedarline
indigo - Vantage Paper
violet - Peony Florals
pink - Redwood Metals
red - Orchard Street
orange - Baker & Lowe Supply
amber - Greenline Packaging
green - Halcyon Logistics
teal
import { MonogramTile } from "@oration/canon/components/monogram-tile";import { TAG_COLORS } from "@oration/canon/components/tag";export function Colors() { const companies: Record<(typeof TAG_COLORS)[number], string> = { gray: "Twilio", blue: "Northwind Freight", indigo: "Cedarline", violet: "Vantage Paper", pink: "Peony Florals", red: "Redwood Metals", orange: "Orchard Street", amber: "Baker & Lowe Supply", green: "Greenline Packaging", teal: "Halcyon Logistics", }; return ( <ul className="grid w-full max-w-xl grid-cols-2 gap-x-6 gap-y-3 sm:grid-cols-3"> {TAG_COLORS.map((color) => ( <li key={color} className="flex min-w-0 items-center gap-2.5"> <MonogramTile name={companies[color]} color={color} size="lg" /> <span className="flex min-w-0 flex-col"> <span className="truncate text-13 text-foreground"> {companies[color]} </span> <code className="font-mono text-xs text-muted-foreground"> {color} </code> </span> </li> ))} </ul> );}How initials are chosen
The first letters of the first two words, skipping a standalone & and anything that isn't a letter.
import { MonogramTile } from "@oration/canon/components/monogram-tile";export function Initials() { const names = [ "Northwind Freight", "Halcyon", "Baker & Lowe Supply", "3PL Direct", ]; return ( <div className="grid w-full max-w-md grid-cols-[1fr_auto_auto] items-center gap-x-6 gap-y-3 text-13"> <span className="text-xs text-muted-foreground">Name</span> <span className="text-xs text-muted-foreground">md</span> <span className="text-xs text-muted-foreground">sm</span> {names.map((name) => ( <div key={name} className="contents"> <span className="text-foreground">{name}</span> <MonogramTile name={name} color="gray" size="md" /> <MonogramTile name={name} color="gray" size="sm" /> </div> ))} </div> );}Providers
AI, speech and telephony providers are gray tiles with the name beside them, so none reads as featured.
Deepgram
Speech to text with interim results.
ElevenLabs
Voices for outbound W-9 and payment calls.
Twilio
Phone numbers and SIP trunking.
import { Button } from "@oration/canon/components/button";import { MonogramTile } from "@oration/canon/components/monogram-tile";import { toast } from "@oration/canon/components/toast";export function Providers() { const providers = [ { name: "Deepgram", description: "Speech to text with interim results.", connected: true, }, { name: "ElevenLabs", description: "Voices for outbound W-9 and payment calls.", connected: true, }, { name: "Twilio", description: "Phone numbers and SIP trunking.", connected: false, }, ]; return ( <ul className="grid w-full max-w-2xl gap-3 sm:grid-cols-3"> {providers.map((provider) => ( <li key={provider.name} className="flex flex-col gap-3 rounded-xl bg-card p-4 shadow-border" > <div className="flex items-start gap-3"> <MonogramTile name={provider.name} color="gray" /> <div className="flex min-w-0 flex-col"> <p className="text-sm font-medium text-foreground"> {provider.name} </p> <p className="text-13 text-muted-foreground"> {provider.description} </p> </div> </div> <Button type="button" variant="outline" size="sm" className="self-start" onClick={() => toast.add({ title: provider.connected ? `${provider.name} settings opened` : `Connect ${provider.name}`, description: provider.connected ? undefined : "Add an API key to start using it.", }) } > {provider.connected ? "Manage" : "Connect"} </Button> </li> ))} </ul> );}Inline in text
At xs, the tile sits on the text baseline beside the supplier's name inside a sentence, as approvals on Home do.
Approve early payment
Pay Halcyon Logistics for INV-20931 today to take the 2% discount, saving $1,827.54.
import { Button } from "@oration/canon/components/button";import { MonogramTile } from "@oration/canon/components/monogram-tile";import { toast } from "@oration/canon/components/toast";export function InlineMention() { return ( <div className="flex w-full max-w-md flex-col gap-2 rounded-xl bg-card p-4 shadow-border"> <p className="text-sm font-semibold text-foreground"> Approve early payment </p> <p className="text-13 leading-5 text-pretty text-foreground/90"> Pay{" "} <span className="inline-flex items-center gap-1 align-[-3px] font-medium text-foreground"> <MonogramTile name="Halcyon Logistics" color="teal" size="xs" /> Halcyon Logistics </span>{" "} for INV-20931 today to take the 2% discount, saving $1,827.54. </p> <div className="flex justify-end gap-2 pt-1"> <Button type="button" variant="ghost" size="sm" onClick={() => toast.add({ title: "Skipped", description: "INV-20931 stays on the Friday run.", }) } > Skip </Button> <Button type="button" size="sm" onClick={() => toast.add({ type: "success", title: "Early payment approved", description: "Halcyon Logistics will be paid today.", }) } > Approve </Button> </div> </div> );}States#
| State | Treatment |
|---|---|
| Static | A monogram has no interactive states. Hover and focus belong to the row or card that holds it. |
| One letter | At xs and sm, or when the name is one word: Halcyon is H. |
| Neutral | color="gray" for providers and integrations, so no brand stands out. |
Behavior#
- Initials come from the first letter of the first two words. A standalone
&is skipped, so Baker & Lowe Supply is BL. - Characters other than A to Z, spaces and
&are stripped before the initials are taken, so digits and punctuation never show. - The tile is always
aria-hidden; the name beside it is what people and screen readers read. - It renders a
<span>, so it sits inline with text atxsand aligns withitems-centerin rows.
Do and don't#
Content#
- Pass the name people know, not the legal entity: Northwind Freight, not Northwind Freight Holdings, LLC.
- When a name starts with a digit or symbol, the tile skips it: 3PL Direct draws PD. Check that the letters still make sense.
- Keep the name visible beside the tile. The letters alone don't identify a company.
Accessibility#
- Always
aria-hidden. There is notitleprop, so never use a tile without the company's name next to it. - The tag fill and deep ink pairs are the ones tags use for 12px text. At
xs,smandmdthe letters are 8 to 10px, so treat them as a shape, not as text anyone must read. - No motion and no focus.
Design tokens#
| Token | Used for |
|---|---|
--tag-{color}, --tag-{color}-fg | Fill and initials for each of the ten tag colors |
outline-black/5, dark:outline-white/10 | Inset 1px edge |
rounded-md, rounded-lg, rounded-xl | 8, 10 and 12px corners at md, lg and xl |
API reference#
MonogramTile
A letter tile for an organization. Also exports the type MonogramSize.
| Prop | Type | Default | Description |
|---|---|---|---|
nameRequired | string | No default | The organization's name. Initials are derived from it. |
colorRequired | "gray" | "blue" | "indigo" | "violet" | "pink" | "red" | "orange" | "amber" | "green" | "teal" | No default | A TagColor. gray for providers; the record's own color for companies, teams and workspaces. |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | 16, 20, 24, 32 or 48px. |
className | string | No default | Merged last. |
Known gaps#
Where the implementation and the system disagree today. Follow the system, not the gap.
Initials at xs, sm and md are 8, 9 and 10px, under the 11px floor in The Thirteen-Fourteen Rule.
The 4px and 5px corners at xs and sm are outside the 6, 8, 10 and 12px radius ramp.
Initials strip every character outside A to Z, so accented first letters vanish: Émile Roux becomes MR and Ørsted becomes R.
There is no title or label prop, so the tile can't be used on its own accessibly.
color is always passed by hand from the record. There is no seed option, while Avatar and the contact center derive tints from identityColors(seed), and indigo is allowed here but excluded from IDENTITY_HUES.
Most of the product imports it through apps/web/src/components/records/avatars.tsx, which re-exports it and wraps it as CompanyLogo, and duplicates its tint and size maps for its own avatars. The adoption count on this page undercounts real use.