Chapter 03 · Foundations · Icon system

Icons. Forty-plus glyphs.

The kit’s icon family. Roughly forty glyphs across three sub-families — CRM monoline icons, file-type icons (with a smart dispatcher), and miscellaneous task / source-control / assorted glyphs. All monoline, all 18×18 by default with the size prop driving alternate renderings, and all themable via color: currentColor on any parent. Live in a single source file at packages/ui/src/components/icons.tsx and re-exported from the package barrel.

3.1 CRM monoline icons

The CRM nav-rail set. Twenty-five monoline glyphs covering the canonical Spark CRM entities — contacts, companies, leads, deals, tickets, sequences, and the rest of the platform’s primary objects. Every glyph fits the same 18×18 viewBox, uses a 1.5 stroke, and inherits currentColor. Drop one in front of any nav label, button label, or section header to mark the object type.

IconFamily · CRM set

.ic-cell

Every CRM icon in the kit, rendered at 32px above its import name. Hover or focus a cell to surface a subtle highlight. Each cell’s SVG is the same payload the React component renders; only the wrapping <svg> width + height change with the size prop.

HomeIcon
CheckIcon
PersonIcon
BuildingIcon
SproutIcon
BriefcaseIcon
FolderIcon
WandIcon
MegaphoneIcon
HeadphonesIcon
BookIcon
StarIcon
RefreshIcon
RocketIcon
HandshakeIcon
CoinIcon
ChartLineIcon
ChartBarIcon
SettingsIcon
PenIcon
CalendarIcon
UsersIcon
ScrollIcon
SearchIcon
PlusIcon
<!-- Each icon is a tiny inline SVG sharing the same viewBox + stroke. -->
<!-- size=18 default; pass width/height to render larger (32 here).    -->
<!-- Colour comes from `color: currentColor` on the parent. The cell    -->
<!-- wrapper labels the glyph with its import name in mono type.       -->
<div class="ic-cell">
  <span class="ic-cell-glyph" aria-hidden="true">
    <svg width="32" height="32" viewBox="0 0 18 18"
         fill="none" stroke="currentColor" stroke-width="1.5"
         stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="M2.75 8.5L9 3l6.25 5.5V14a1 1 0 0 1-1 1h-3v-4.25h-4.5V15h-3a1 1 0 0 1-1-1V8.5Z"/>
    </svg>
  </span>
  <span class="ic-cell-name">HomeIcon</span>
</div>
/* Chapter-local catalogue grid. Auto-fills cells at ~120px; each cell
   centres a glyph above its mono import name. Hover lifts the cell
   background subtly so the operator can scan + zero in on a name.     */
.ic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--s-3);
}
.ic-cell {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-3);
  background: var(--bg-paper);
  border: 1px solid var(--hair);
  border-radius: var(--r-md);
  transition: background 120ms ease, border-color 120ms ease;
}
.ic-cell:hover,
.ic-cell:focus-within {
  background: var(--bg-warm);
  border-color: color-mix(in oklab, var(--ink) 16%, var(--hair));
}
.ic-cell-glyph { color: var(--ink); width: 32px; height: 32px; }
.ic-cell-name  { font: 500 11px/1.2 var(--f-mono); color: var(--fg-soft); }
// Icons are plain inline SVGs — no inline JS required for the catalogue.
// In live product UI you typically render them via the React component
// (see the React tab). When you only need raw HTML, you can also drop
// the <svg> payload into any markup directly; the family is designed to
// inherit `color: currentColor` from any parent.
//
// Programmatic tinting (optional):
document.querySelectorAll('.ic-cell-glyph').forEach((el) => {
  el.style.color = 'var(--accent)';
});
// Every icon is a tiny `forwardRef` component sharing the IconProps
// shape — `size` (px, defaults to 18), `className`, and every native
// SVG attribute. Import any subset by name from the package barrel.
import {
  HomeIcon,
  CheckIcon,
  PersonIcon,
  BuildingIcon,
  SproutIcon,
  BriefcaseIcon,
  FolderIcon,
  WandIcon,
  MegaphoneIcon,
  HeadphonesIcon,
  BookIcon,
  StarIcon,
  RefreshIcon,
  RocketIcon,
  HandshakeIcon,
  CoinIcon,
  ChartLineIcon,
  ChartBarIcon,
  SettingsIcon,
  PenIcon,
  CalendarIcon,
  UsersIcon,
  ScrollIcon,
  SearchIcon,
  PlusIcon,
} from '@magicblocksai/ui';

// Default render — 18px, currentColor.
<HomeIcon />

// Custom size — kept square. Pair with a label (or aria-label).
<HomeIcon size={32} />
<HomeIcon size={32} aria-label="Overview" />

// Coloured via CSS — inherits `color` from any parent.
<span style={{ color: 'var(--accent)' }}>
  <SettingsIcon />
</span>

3.2 File-type icons

Seven file-type glyphs sharing a “page with corner fold” base — the inner mark distinguishes the type. Pair with the <FileTypeIcon contentType="…" /> dispatcher when you have a MIME string and want the kit to pick the right glyph. <FilePdfIcon> carries a typographic “PDF” label as a scoped exception — at 18×18 the format isn’t otherwise distinguishable from a generic document.

File-type set & FileTypeIcon dispatcher

.ic-strip

The seven base glyphs in a horizontal strip, then three dispatcher examples beneath — application/pdf, image/png, and application/zip — resolving to FilePdfIcon, FileImageIcon, and FileArchiveIcon. The dispatcher normalises content-type to lowercase, trims whitespace, and falls back to FileIcon for any string that doesn’t match a specific branch.

FileIcon
FileTextIcon
FileImageIcon
FilePdfIcon
FileVideoIcon
FileAudioIcon
FileArchiveIcon
application/pdf FilePdfIcon
image/png FileImageIcon
application/zip FileArchiveIcon
<!-- The seven file-type glyphs share the same page-with-fold base.   -->
<!-- Drop the inner mark in place to distinguish the format. The PDF  -->
<!-- glyph carries a typographic "PDF" label as a scoped exception.    -->
<div class="ic-strip">
  <div class="ic-cell">
    <span class="ic-cell-glyph" aria-hidden="true">
      <svg width="32" height="32" viewBox="0 0 18 18" fill="none"
           stroke="currentColor" stroke-width="1.5"
           stroke-linecap="round" stroke-linejoin="round">
        <path d="M4 2.75A1 1 0 0 1 5 1.75H10.5L14 5.25V15.25A1 1 0 0 1 13 16.25H5A1 1 0 0 1 4 15.25Z"/>
        <path d="M10.5 1.75V5.25H14"/>
      </svg>
    </span>
    <span class="ic-cell-name">FileIcon</span>
  </div>
  <!-- …six more cells (Text / Image / Pdf / Video / Audio / Archive) -->
</div>

<!-- Dispatcher row — visualises the contentType→icon mapping.         -->
<div class="ic-dispatch-row">
  <span class="ic-dispatch-ct">application/pdf</span>
  <span class="ic-dispatch-arrow">&rarr;</span>
  <span class="ic-dispatch-resolved">FilePdfIcon</span>
  <span class="ic-dispatch-glyph"><svg…/></span>
</div>
/* Horizontal strip for the file-type set + a dispatch row pairing a
   contentType pill with the resolved icon name and a rendered glyph. */
.ic-strip {
  display: flex; flex-wrap: wrap; align-items: stretch;
  gap: var(--s-3);
}
.ic-strip .ic-cell { flex: 1 1 120px; min-width: 120px; }

.ic-dispatch {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--s-3);
}
.ic-dispatch-row {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--bg-paper);
  border: 1px solid var(--hair);
  border-radius: var(--r-md);
}
.ic-dispatch-ct {
  font: 500 11px/1 var(--f-mono); color: var(--fg-soft);
  padding: 4px 8px; background: var(--bg-sunk);
  border-radius: var(--r-sm);
}
.ic-dispatch-arrow    { color: var(--fg-faint); }
.ic-dispatch-resolved { font: 500 11px/1 var(--f-mono); color: var(--ink); }
.ic-dispatch-glyph    { margin-left: auto; color: var(--ink); }
// The dispatcher's mapping logic in vanilla JS — mirrors the
// `getFileTypeKind` exported from `@magicblocksai/ui` for consumers
// rendering icons outside React.
function getFileTypeKind(contentType) {
  const ct = (contentType ?? '').toLowerCase().trim();
  if (!ct) return 'doc';
  if (ct.startsWith('image/')) return 'image';
  if (ct === 'application/pdf') return 'pdf';
  if (ct.startsWith('video/')) return 'video';
  if (ct.startsWith('audio/')) return 'audio';
  if (/(zip|tar|gzip|7z|rar|bzip|xz)/i.test(ct)) return 'archive';
  if (/(text\/|application\/(json|xml|yaml|x-csv|jsonl))/i.test(ct)) return 'text';
  return 'doc';
}

getFileTypeKind('application/pdf');   // → 'pdf'
getFileTypeKind('image/png');         // → 'image'
getFileTypeKind('application/zip');   // → 'archive'
getFileTypeKind('application/x-yaml');// → 'text'
getFileTypeKind('');                  // → 'doc'
// Import any individual glyph by name, or use the dispatcher to pick
// the right one from a content-type string. Both surfaces share the
// standard IconProps shape (size, className, aria-label, etc.).
import {
  FileTypeIcon,
  FileIcon,
  FileTextIcon,
  FileImageIcon,
  FilePdfIcon,
  FileVideoIcon,
  FileAudioIcon,
  FileArchiveIcon,
  getFileTypeIcon,
} from '@magicblocksai/ui';

// Direct — when the type is known at compile time.
<FilePdfIcon size={18} aria-label="PDF" />

// Dispatcher — when you have a MIME string at render time.
<FileTypeIcon contentType="application/pdf" size={18} />
<FileTypeIcon contentType="image/png"       size={18} />
<FileTypeIcon contentType="application/zip" size={18} />

// As a prop / in a render list — resolve the component once.
const Icon = getFileTypeIcon(file.content_type);
<Icon size={18} aria-label={`${file.name} (${file.content_type})`} />

3.3 Misc

Sixteen glyphs that don’t belong to the CRM nav set or the file-type family — task-type icons (clock, eye, phone, trash), source-control marks (GitHub, branch), and a grab-bag of utility shapes (bolt, inbox, link, mail, menu, platform, route, shield, sync, target). Same monoline style as the CRM set; same currentColor theming.

IconFamily · misc set

.ic-cell

Every miscellaneous icon in the kit, rendered at 32px above its import name. The mix covers task-type indicators (phone call, SLA clock, review eye, destructive trash), source-control glyphs (the Octocat and a branch fork), and a set of utility shapes that span feedback (bolt, target, shield), navigation (menu, route, link), and CRM-adjacent surfaces (inbox, mail, platform, sync).

ClockIcon
EyeIcon
TrashIcon
PhoneIcon
BoltIcon
InboxIcon
LinkIcon
MailIcon
MenuIcon
PlatformIcon
RouteIcon
ShieldIcon
SyncIcon
TargetIcon
GithubIcon
BranchIcon
ExpandIcon
BellIcon
SunIcon
MoonIcon
SystemThemeIcon
MessageCircleQuestionIcon
ExternalLinkIcon
HeartIcon
MoreVerticalIcon
DownloadIcon
UploadIcon
GlobeIcon
MessageSquarePlusIcon
FlaskIcon
TrendingDownIcon
UserCogIcon
ArrowRightIcon
SmartphoneIcon
MessageSquareIcon
CodeIcon
ChevronDownIcon
ChevronUpIcon
ChevronLeftIcon
ChevronRightIcon
<!-- Same inline-SVG pattern as the CRM set. The misc icons follow the -->
<!-- same 18×18 / 1.5 stroke / currentColor / round caps spec.          -->
<div class="ic-cell">
  <span class="ic-cell-glyph" aria-hidden="true">
    <svg width="32" height="32" viewBox="0 0 18 18" fill="none"
         stroke="currentColor" stroke-width="1.5"
         stroke-linecap="round" stroke-linejoin="round">
      <circle cx="9" cy="9" r="6.25"/>
      <path d="M9 5.5V9l2.5 1.75"/>
    </svg>
  </span>
  <span class="ic-cell-name">ClockIcon</span>
</div>
/* The misc grid reuses the same .ic-grid / .ic-cell wrapper as 22.1. */
.ic-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--s-3);
}
.ic-cell {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-3);
  background: var(--bg-paper);
  border: 1px solid var(--hair);
  border-radius: var(--r-md);
}
// No inline JS needed — these are plain inline SVGs. Tint via CSS:
//
//   :where(.toolbar, .nav, .sidebar) .ic-cell-glyph { color: var(--accent); }
//
// Or, swap colour on a state change:
function setIconTone(el, tone) {
  el.style.color = tone === 'danger'
    ? 'var(--error)'
    : tone === 'success'
    ? 'var(--success)'
    : 'var(--ink)';
}
// The misc set is barrel-exported alongside the CRM and file-type sets.
import {
  ClockIcon,
  EyeIcon,
  TrashIcon,
  PhoneIcon,
  BoltIcon,
  InboxIcon,
  LinkIcon,
  MailIcon,
  MenuIcon,
  PlatformIcon,
  RouteIcon,
  ShieldIcon,
  SyncIcon,
  TargetIcon,
  GithubIcon,
  BranchIcon,
  ExpandIcon,
  BellIcon,
  SunIcon,
  MoonIcon,
  SystemThemeIcon,
  MessageCircleQuestionIcon,
  ExternalLinkIcon,
  HeartIcon,
  MoreVerticalIcon,
  DownloadIcon,
  UploadIcon,
  GlobeIcon,
  MessageSquarePlusIcon,
  FlaskIcon,
  TrendingDownIcon,
  UserCogIcon,
  ArrowRightIcon,
  SmartphoneIcon,
  MessageSquareIcon,
  CodeIcon,
  ChevronDownIcon,
  ChevronUpIcon,
  ChevronLeftIcon,
  ChevronRightIcon,
} from '@magicblocksai/ui';

// Default render.
<ClockIcon />

// Larger render with an accessible label.
<TrashIcon size={24} aria-label="Delete row" />

// Tinted via CSS — inherits `color` from any parent.
<span style={{ color: 'var(--error)' }}>
  <TrashIcon />
</span>

// Inside an IconButton (chapter 3).
<IconButton aria-label="Open menu">
  <MenuIcon />
</IconButton>