Skip to content

UI Primitives — A Field Guide

If you’ve ever looked at a modern app and thought “what do they call that thing?” — this page is for you.

Every interface you use is built from a surprisingly small set of reusable building blocks called UI primitives. They have names. Weird names, sometimes. But once you see the metaphor, you can’t unsee it.

En Parlant~ uses Mantine, a React component library that provides all of these out of the box. Here’s your field guide.


A toast is a small notification that pops up from the edge of the screen, hangs around for a few seconds, and then slides away. Like a piece of bread popping out of a toaster — except gravity works in reverse and it eventually goes back in.

In En Parlant~, toasts appear in the bottom-right corner (via Mantine’s @mantine/notifications) for things like:

  • “Database loaded successfully” — pop
  • “TTS voice not configured” — pop
  • “Failed to connect to Lichess” — pop, and you feel a little sad

You never asked for them. They just show up, deliver their message, and leave. They’re the carrier pigeons of the UI world.

A card is a rectangular container with a border or shadow that groups related content together. Picture an index card sitting on a desk — it has a defined edge, it holds one thing, and you can pick it up and move it.

In En Parlant~, you’ll see cards for:

  • Game previews in the database browser (GameCard) — player names, result, date
  • Board position editor (EditingCard) — the card you flip over when setting up a custom position
  • Database entries on the databases page — each database gets its own card in a grid

Cards have a close cousin called Paper, which is basically a card that went to art school. Same idea — a container with a border or shadow — but with less semantic meaning. Paper just says “I’m a surface.” It shows up as the backdrop behind analysis panels and database info displays.

Not currently used in En Parlant~, but worth knowing: a drawer is a panel that slides in from the edge of the screen, like a desk drawer. (That’s it. That’s the whole metaphor. UI designers were very literal that day.)


A modal is a popup dialog that demands your attention. It greys out the background and makes you deal with it before doing anything else. It’s the toddler of UI patterns — “LOOK AT ME RIGHT NOW.”

En Parlant~ uses modals for moments that genuinely need your focus:

  • “Are you sure you want to clear all data?” (ClearDataModal) — with checkboxes so you can choose which data to obliterate
  • “An update is available” (UpdateModal) — with a progress bar while it downloads
  • “Import a game” (ImportModal) — paste a PGN, enter a FEN, or drop a link
  • “Create a new repertoire” (CreateRepertoireModal) — name it, configure it, go
  • “About En Parlant~” (About) — version info, credits, the usual backstage pass

Modals say: “This is a conversation. We’re having it now.”

A dialog is a modal’s polite cousin. In Mantine, they’re essentially the same component, but in UI design theory, a dialog is smaller and more focused — a single question and answer. “Save changes?” Yes / No. Done.

En Parlant~ uses ConfirmModal for these quick yes-or-no moments, like confirming you want to discard unsaved changes.


A tooltip is a tiny label that appears when you hover over something. It’s the helpful stranger who whispers the answer when you look confused.

Tooltips are everywhere in En Parlant~. Hover over almost any icon button and you’ll get one:

  • Hover the ↻ button → “Reload”
  • Hover a truncated file path → the full path appears
  • Hover an annotation symbol → what that symbol means
  • Hover a move in the analysis panel → a miniature board preview pops up

Tooltips are the original “no manual required.”

A popover is a tooltip that went to graduate school. Instead of just showing a text label, it can contain anything — buttons, images, even a tiny chessboard.

In En Parlant~, when you hover over a move in the engine analysis, a popover renders a preview of the board at that position. It’s a tooltip, but it brought visual aids.

A menu is a list of actions that appears when you click (or right-click) something. You’ve used these since the dawn of computing — File, Edit, View. You know the drill.

En Parlant~ has two flavours:

  • Top bar menus — the classic File / Edit / View menu bar, built with Mantine’s Menu component. Keyboard shortcuts listed on the right. A design pattern so old it has a pension.
  • Context menus — right-click on a move in the game tree and you get options like Promote Variation, Delete from Here, and Copy Line. These use the mantine-contextmenu library for extra polish.

Menus have their own sub-vocabulary: Menu.Target (the thing you click), Menu.Dropdown (the list that appears), Menu.Item (each option), and Menu.Divider (the thin line that says “different section now”).


These are the peanut butter and jelly of layout.

  • Stack arranges things vertically — one on top of another. Like stacking books.
  • Group arranges things horizontally — side by side. Like lining up chess pieces on a shelf.

They appear in literally 100+ files each. They’re the most common components in the entire app. Every time things are neatly arranged, a Stack or Group is responsible.

Flex is Stack and Group’s Swiss Army knife cousin. When things need to wrap, stretch, shrink, reverse, or otherwise misbehave, Flex steps in with full control over the CSS Flexbox model.

Used in the analysis panel’s move list — those sequences of moves that wrap and flow like text? That’s Flex doing the heavy lifting.

A Box is a <div> wearing a top hat. It does nothing on its own — it’s just a generic container. But it accepts Mantine’s styling props, which makes it an incredibly handy wrapper for “I need a thing around this other thing.”

Box appears in 150+ files. It is the duct tape of the component library.

Does exactly what it says. Puts something in the center. Horizontally. Vertically. Both. Done.

Shows up in empty states — when there are no accounts linked, no databases loaded, no data yet. A big icon, a message, and Center making sure it’s perfectly positioned. Nobody likes an off-center empty state.

A grid that’s simple. You tell it how many columns and it arranges your cards in a neat grid that reflows as the window resizes. The database page uses it to lay out database cards in a responsive grid.

A container that scrolls when its content overflows. You’d think this would be free — and it sort of is, with CSS overflow: auto — but ScrollArea adds styled scrollbars, momentum scrolling, and overflow detection. It wraps the game tree panel, analysis lines, and anywhere content might grow taller than its box.

The AppShell is the master layout — the skeleton of the entire application. It defines where the header goes, where the sidebar goes, and where the main content lives. Everything else nests inside it.

You only use it once. It’s the foundation of the house.

A Portal is a magical wormhole. It renders a component outside its normal position in the DOM tree and places it somewhere else — usually at the root of the document. This is how popovers and board previews avoid being clipped by parent containers with overflow: hidden.

You never see a Portal. You only see its effects. It’s the stage crew of UI components.


A clickable rectangle with text. You know this one. In Mantine, buttons come in variants:

  • filled — solid colour, high emphasis. “Click me, I’m important.”
  • outline — just a border. “I’m an option, but no pressure.”
  • subtle — barely there. “I’m here if you need me.”
  • default — a neutral middle ground.

En Parlant~ uses all of these across 150+ files. The most common pattern: a Group of buttons at the bottom of a modal — “Cancel” (subtle) and “Confirm” (filled). Classic.

An ActionIcon is a button that gave up its text label and committed fully to the icon lifestyle. It’s compact, square, and relies on its icon (plus a tooltip) to communicate its purpose.

Toolbar buttons, panel controls, close buttons — all ActionIcons. They’re the most space-efficient way to offer an action.

A Switch is a toggle that looks like a tiny light switch. Flip it left, flip it right. On. Off.

In settings, switches control binary choices:

  • Sound on/off
  • TTS narration enabled/disabled
  • Dark mode / light mode

The physical metaphor is so good that even your grandparents understand it.

A Slider is a track with a handle you drag back and forth to select a value. Like a volume knob that got flattened.

En Parlant~ uses sliders for:

  • Font size — drag to adjust
  • Volume — drag to adjust (of course)
  • CPU cores — how many cores should the engine use?
  • Hash memory — how much RAM for the engine’s hash table?

There’s also a RangeSlider with two handles for selecting a range — used for filtering games by Elo rating (e.g., 2000–2200) and time periods.

A row of buttons where exactly one is selected at a time. It’s like a car radio preset — push one, the others pop out.

Used for toggling between:

  • Centipawn evaluation vs. Win/Draw/Loss percentages on the analysis chart
  • Different move notation formats
  • Engine line count

You know these. Checkboxes for “pick any that apply,” Select dropdowns for “pick one from this list.” They show up in settings, filters, and forms throughout the app. The Clear Data modal has a particularly satisfying set of checkboxes where you can choose your own destruction.

A text input that suggests completions as you type. The player search in the database browser uses this — start typing a name and matching players appear below.

FileInput opens a file picker when clicked — used in the import modal for loading PGN files. DateInput pops open a calendar for selecting a date — used for filtering games by date. There are also MonthPickerInput and YearPickerInput for when you need less precision (Lichess database filtering by month, Masters database filtering by year).


A horizontal bar that fills from left to right to show how far along something is. Used during app updates (download progress) and — more creatively — to show Win/Draw/Loss probabilities in the analysis panel. Three coloured sections (white, grey, black) fill the bar proportionally. It’s a tiny bar chart disguised as a progress indicator.

A Loader is a spinning animation that says “I’m working on it, give me a second.” Shows up while databases are loading, player stats are being fetched, or anything async is happening.

A Skeleton is a grey placeholder that mimics the shape of content that hasn’t loaded yet. Instead of a blank screen or a spinner, you see ghost outlines of where cards and text will be. It’s the architectural blueprint of a loading state — “the real furniture is on its way.”

The database page uses skeletons while the card grid loads. Much more elegant than a spinner alone.

A semi-transparent blanket thrown over a component while it’s loading. The analysis chart uses this — you can still see the chart behind the overlay, but it’s dimmed, and there’s a spinner on top. “I’m still here, just updating.”


A Badge is a small coloured label — like a name tag at a conference. In En Parlant~, the SpeedBadge component uses badges to display time controls with colour coding:

  • 🩷 Pink — UltraBullet
  • 🔴 Red — Bullet
  • 🟠 Orange — Blitz
  • 🟢 Green — Rapid
  • 🔵 Blue — Classical
  • 🟣 Purple — Correspondence

Each badge is tiny, colourful, and instantly communicates the game speed at a glance.

Short for keyboard. Displays a keyboard key with that characteristic raised-key styling. Used in the keybinding settings to show things like Ctrl + Z. It’s a CSS recreation of an actual physical key on your keyboard. Delightfully skeuomorphic.


A horizontal line that separates sections. That’s it. It’s a line. But it’s a styled line, and it can optionally have a text label in the middle — like the ”— OR —” divider in the import modal that separates “paste a PGN” from “enter a FEN.”

Thirty-plus files use Divider. Turns out, things need separating a lot.

A Collapse is a section that expands and contracts with a smooth animation. Click to reveal, click to hide. The database filter options use this — you don’t always need to see every filter, so they tuck away behind a Collapse until you want them.


A full what-you-see-is-what-you-get text editor, built on TipTap. Bold, italic, lists, the works. Used for writing game annotations — those commentary notes you add to explain why a move was brilliant or terrible.

This is the one primitive that’s more of a compound organism than a single cell. Under the hood, it’s TipTap + ProseMirror + Mantine styling. But from the user’s perspective, it’s just a text box where formatting works.

From @mantine/charts (which wraps Recharts), the AreaChart powers the evaluation graph — that waveform running along the bottom of the analysis panel. White area above the midline means White is winning; black below means Black is winning.

You can click any point on the chart to jump to that move. Interactive data visualisation meets chess analysis. It even has custom tooltips and reference lines marking the game phases (opening → middlegame → endgame).

Via the mantine-datatable library — a full-featured data table with sorting, pagination, row selection, and filtering. Powers the database browser’s game list, player list, and tournament list. It’s a spreadsheet that knows it’s in a chess app.


Some primitives don’t render anything visible themselves but make everything else work:

  • MantineProvider — wraps the entire app and provides the theme (colours, dark mode, component defaults)
  • Portal — teleports rendered content to a different part of the DOM
  • useHotkeys — a hook (not a component) that wires up keyboard shortcuts
  • useForm — manages form state, validation, and error handling
  • useDebouncedValue — waits for you to stop typing before firing a search. The polite hook.

For reference, here’s every primitive family used in En Parlant~, grouped by what they do:

CategoryPrimitives
LayoutAppShell, Box, Center, Flex, Group, Stack, SimpleGrid, ScrollArea, Portal
SurfacesCard, Paper
OverlaysModal, Tooltip, Popover, Menu, ContextMenu, LoadingOverlay
NotificationsToast (via @mantine/notifications)
ButtonsButton, ActionIcon, CloseButton, ThemeIcon
InputsTextInput, Textarea, NumberInput, Select, Autocomplete, Checkbox, Switch, Slider, RangeSlider, SegmentedControl, FileInput, DateInput, MonthPickerInput, YearPickerInput
Data displayTable, DataTable, Badge, Code, Kbd, Rating, Image
TypographyText, Title, Anchor
ProgressProgress, Loader, Skeleton
StructureDivider, Collapse
Rich contentRichTextEditor (TipTap), AreaChart (Recharts)
ThemingMantineProvider, createTheme, useColorScheme
HooksuseForm, useHotkeys, useToggle, useDebouncedValue, useClickOutside, useElementSize

All sourced from Mantine v8 and its ecosystem packages: @mantine/core, @mantine/notifications, @mantine/dates, @mantine/charts, @mantine/form, @mantine/tiptap, mantine-datatable, and mantine-contextmenu.


Next time you see a little message slide up from the corner of your screen — that’s a toast. The rectangle around a game preview — that’s a card. The grey blobs flickering before content loads — those are skeletons.

Once you learn the names, you start seeing them everywhere. Every app. Every website. The same couple dozen primitives, rearranged in a thousand different ways.

It’s Lego, all the way down.