Character · Text · Icons & symbols · Colour · Surfaces · Shape & space · Components · Placement · Motion & words · Accessibility · Enforcement · Screens

Design System

The rules every screen follows. Views use tokens, never raw values: no .font(.caption), no Color(white: 0.2), no cornerRadius: 12, no .padding(14).

Tokens live in Design/Mockup/Mockup/DesignSystem.swift, shared parts in Components.swift, and Design/Mockup/.swiftlint.yml fails the build on anything else. Every image on this page is a crop of the working mockup, shown at its real point size.

Character

A working tool for people who navigate for a living: search and rescue, surveyors, soldiers. Dark only. Dense, legible, calm. Solid boxes you can hit with a cold thumb. The map is the star; the chrome stays out of its way. It should feel closer to an instrument than to a lifestyle app.

Text

Fifteen roles, applied with .textRole(_:). Each is a Dynamic Type style, so all text scales with the user's text size. Samples are shown at the default size.

RoleSampleStyleFor
titleMy LocationTitle 2, boldEvery panel's title, always with a subtitle
readout10U DV 89755Large Title, mono, semiboldYour coordinate
heading270° WTitle, semibold, mono digitsCompass heading and the turn
value879 mTitle 2, medium, mono digitsA data cell's value
valueCode10U DV 90114 02569Title 2, mono, mediumA coordinate in a data cell
headlineJunctionHeadlineA name in a card or banner
bodySea to Summit TrailBodyList rows, descriptions
detail469 m @ 130° SESubheadline, mono digitsDistance and bearing under a name
caption9 items · May 18, 2023CaptionCounts, dates, footers, notes
labelCoordinateCaption, bold, uppercase, 0.6 trackingMetadata only: cell titles, tags, kickers
buttonGo HereFootnote, semibold, Title CaseBox buttons, chips, bulk actions
badgeLocation Services OffFootnote, semiboldMap badges, Stop
badgeCode10U DV 89610 02760 · 655 mFootnote, mono, semiboldA coordinate in a badge
mapLabelCKP 3Subheadline, bold, with a haloA marker's name on the map
tabCompassCaption 2, semiboldTab row titles
Numbers. Coordinates are fully monospaced so grid digits line up. Anything that changes while you watch (distance, bearing, speed, time) uses mono digits so it does not jitter. Prose never uses mono.
Uppercase is for metadata, never buttons. Cell titles, tags and kickers are label; buttons are Title Case button. Never call .uppercased().
Titles are modest. No Large Title. Every header is a title with a subtitle that says something useful. The compass heading and turn are the one exception, in heading.
Text colour. Primary by default. Secondary for detail and caption, and only on a solid surface. On glass, text is primary.
Drawn text. Compass numerals and marker symbols scale with their drawing, not Dynamic Type. They use Font.drawn(_:), the only fixed size allowed.
My Location header
Header: title with a detail subtitle that earns its place.
Location coordinate readout
label title, readout coordinate, caption accuracy.

Icons

SF Symbols only. A symbol inside text takes the text's role. On its own, a symbol uses one of two sizes.

IconSize.control · 17 pt semibold

Header circles and the map capsule (base map, 3D, follow).

Map capsule

IconSize.large · 20 pt semibold

Box buttons, the tab row, the launcher, the banner arrow, the turn indicator.

Launcher button

One symbol per idea

Every symbol the app uses is named in Symbol. Two ideas never share a symbol, and one idea never has two.

IdeaSF SymbolIdeaSF Symbol
Location tab, a coordinatescopeNavigate, Go Herearrow.triangle.turn.up.right.diamond.fill
Compass tablocation.north.circle.fillStopxmark.octagon.fill
Library tab, a layersquare.3.layers.3dFollow, my locationlocation.fill
Settings tabgearshape.fillMap centreplus.viewfinder
Elevationmountain.2Distance, Measureruler
Bearinglocation.north.line.fillCourselocation.north.line
DeclinationangleSpeedspeedometer
Routepoint.topleft.down.to.point.bottomright.curvepathAreaskew
Trackpoint.bottomleft.forward.to.point.topright.scurvepathRecordrecord.circle
Go to Coordinatelocation.viewfinderMarker at Coordinatenumber
Download offline maparrow.down.circleImportsquare.and.arrow.down
Location Services Offlocation.slash.fillPoor GPSexclamationmark.triangle.fill
Buyheart.fillTurn left / right / on bearingarrow.turn.up.left · .right · arrow.up

Colour

Colour has a job. Nothing is coloured for decoration. Every colour goes through Palette, so a red night-vision mode is a second palette, not a second app.

#FFD60A
accentPrimary action, selected state, the navigation target
#FF453A
dangerStop, Delete, Location Services Off, recording
#000000
onAccentText and symbols on accent
#FFFFFF
onDanger · selectionText on danger; the selected marker's ring and the crosshair
#17171A
panelSolid panels, menus, bars
tertiarySystemFill
raisedData cells and list rows on a panel
#3D3D3D
controlBox buttons and chips
white 14%
hairlineThe one-point edge on solid shapes
markerBackdropBlack 35%: the disc behind a marker
Secondary  Tertiary
.secondary · .tertiarySystem text colours, on solid surfaces only

Markers

Eight colours (red, orange, yellow, green, cyan, blue, purple, white) and eleven hard-edged shapes, drawn with LandNav 1's own code. Every colour, shape, map label and short code is in Markers.html.

red diamondorange triangleyellow stargreen pennantcyan circleblue circleXpurple xmarkwhite plus

Item colours belong to the map

The colours users give markers, tracks and areas never appear in the chrome, except as that item's own icon.

+
▲
+
●
+
■
+
One accent at a time. A panel has one yellow primary action. The Buy badge sits outside the panel and is loud on purpose.
Ink on yellow is black. Black on the accent is 14.9:1; white is 1.4:1 and fails every accessibility rule. It's why road signs, hazard tape and taxis put black on yellow.
Off is half. Anything switched off, like a hidden layer, draws at 50% opacity.

Surfaces

Glass for quiet things floating over the map, solid for anything dense, and loud solid badges for anything the user should want gone.

⌖  10U DV 89610 02760 · 655 m Location Services Off › ⚠ GPS ± 65 m ● 00:42:10 · 2.3 km ♥ Buy Land Nav ›
Gondola
469 m @ 130° SE from my location
Coordinate
10U DV 90114 02569
MGRS · WGS 84

Glass · .floatingGlass()

Quiet things floating over the map: the tab row, map controls, status badges, the navigation banner, header circles, and the phone sheet at its smallest height.

Loud badges

Solid red for Location Services Off and poor GPS, yellow for Buy Land Nav. Dark or white ink and a small shadow. Status badges (recording, map centre) stay glass.

Solid · .solidSurface(radius:)

Anything dense: the sheet at half and full height, the iPad column, menus, the selection bar, box buttons. Glass over a satellite map is hard to read outdoors.

Shape & space

Corners: 6 · 9 · 18. Chosen from four sets, 4 · 6 · 14 to 10 · 16 · 28: the tightest felt too sharp, the roundest read as iOS 26 bubbles. The phone sheet's corners are the system's.
Boxes are for work, rounds are for floating. Things you read and press inside a panel are rounded rectangles: data cells, box buttons, chips. Things floating over the map are capsules or circles.
small · 6

Tags, chips

medium · 9

Cells, box buttons, menus, bars

large · 18

The iPad column

A 4-point grid

xxs · 2
xs · 4
s · 8 — gaps between buttons and cells
m · 12 — inside a cell, between groups
l · 16 — panel content from the panel's edge
xl · 20

Sizes

TokenPointsFor
touch44Nothing tappable is smaller
boxButton64Height of a box button
chip · badge32Chips and map badges
reachButton48Follow and plus
panelHeader52A panel's title row
banner56The navigation banner

Components

Build screens from these. A new one gets added here before it is used twice.

Panel header

PanelHeader

Every panel's top row: optional leading icon, a title, a detail subtitle, and up to two header circles. It is all that shows at the smallest height.

Item header

Item header

MarkerIcon in the item's own colour, title, detail, and a PanelHeaderButton to close.

Action row

ActionRow

Up to four box buttons: solid fill, one size, button text in Title Case, IconSize.large. The first may be prominent (yellow). Destructive actions are red and never first.

Go Here
Show
Edit
Stop
Data cell

DataCell · CellCard

label title with its symbol, a value or valueCode, and an optional caption footer. The unit of information. On a raised fill.

Chips

BoxChip

Small boxes in a panel toolbar: Select, Sort, Make Active. button text, 32 tall, small radius. A chip turns yellow while its mode is on.

Layer list with ACTIVE tag

List rows and ActiveTag

Inset grouped lists on raised. Row title is body, subtitle caption. The eye toggles a layer; a hidden layer's row drops to 50%. ActiveTag marks where new items go.

Selection bar

Selection bar

Bulk actions above the tab row while selecting. Solid, medium radius, count in headline, actions as chips; Delete is red and last.

Smart lists

Smart lists

Lists that look across every layer: Nearby, In View, Pinned, Recently Added, Tracks, Routes and Areas, Recently Deleted. Later, users can make their own.

Navigation banner

NavigationBanner

The navigation target, top of the badge stack. Yellow-tinted glass capsule, 56 tall. Arrow turns toward the target. Stop is always one tap.

Badge stack

MapBadge and the badge stack

Top leading. The banner first, then badges in one fixed order: Location Services Off, poor GPS, recording, downloading, map centre, Support Land Nav. Four styles: alert (red), warning (orange), nag (yellow) and status (glass). A chevron means tapping it does something.

Launcher

Launcher

The plus button opens every feature that starts from the map. First, what gets added to the active layer, which the kicker names and can change. Then Tools, on a raised block: Measure, Go to Coordinate, Search, Download This Area, Import GPX. Rows 42 tall with the symbol trailing.

Compass tape

Compass tape and TurnIndicator

Heading as a readout. The turn is a symbol and degrees, never words. Numbers on top in mono, ticks below, your heading as a white pointer at the bottom, the bearing as a yellow pointer on top, and a yellow band for the turn still to make.

Drawing panel

DrawingPanel

Drawing a route or an area on the map as it is. It takes over the panel like a selected item, hides the tab row until Done or Cancel, and stays small so the map stays big. Tap the map or Add Point at the crosshair.

Tool panel

Tool panel

Measure and Download This Area work like DrawingPanel: a tool icon on raised, the result as the title (1.11 km, mono when it is a number), at most two DataCells, then an ActionRow with the primary action first and Done last. No tab row until Done or the close circle.

Recording panel header

Running task panel

Something that keeps running, like recording, is not a mode. Its panel covers the tab like a selected item, its badge reopens it, and closing it leaves it running. The live number is the title in heading.

Sun card

Chart in a CellCard

Sun path, elevation profile. Drawn in a Canvas inside a card, one colour from the palette, axis text in Font.drawn mono. No gridlines, no legend: the card title says what it is.

Tab row

PanelTabRow

Four tabs in a glass capsule inside the panel. IconSize.large over tab text. The current tab is accent on a raised capsule.

Placement

Primary action

The first box button, in yellow. One per screen.

Destructive

Red, never first, never yellow. At the bottom of a list or menu.

Browse in the panel

Library, then layer, then item: all inside the panel.

Type in a modal

Anything with a keyboard is a system sheet with Cancel and Done.

Menus or boxes

Box buttons for the three or four things done most. Menus for four or more choices.

Map controls

Base map, 3D and follow in one capsule, top trailing. The launcher alone, bottom trailing, riding the top of the sheet; its menu grows up out of it.

Motion & words

Motion

One animation, Animation.standard: snappy, no bounce, for every state change. Nothing moves on its own except live data. Respect Reduce Motion.

Words

Title Case for buttons, titles and menu items; sentence case for footers and notes. A button is one or two words. Say what happens: "Go Here", not "Update navigation destination". Same thing, same word, everywhere: marker, track, route, area, layer, target.

Accessibility

Enforcement

.swiftlint.yml has six custom rules, all errors, all skipping DesignSystem.swift.

✕ Raw valuesText("GO HERE") .font(.caption2.weight(.bold)) .padding(14) .background(Color(white: 0.24), in: .rect(cornerRadius: 12)) withAnimation(.spring) { … }
✓ TokensText("Go Here") .textRole(.button) .padding(Space.m) .background(Palette.control, in: .rect(cornerRadius: Radius.medium)) withAnimation(.standard) { … }
RuleCatchesUse instead
design_font.font(.caption), .font(.system(size:)).textRole, IconSize, Font.drawn
design_colourColor(white:), Color(red:), .accentColorPalette
design_radiuscornerRadius: 12Radius
design_spacingspacing: 10, .padding(14)Space
design_uppercase.uppercased(), .tracking(), .textCase().textRole(.label)
design_animation.spring, .easeInOut, .snappyAnimation.standard

Screens

Every screen of the mockup, and the layout rules behind them, are in LandNav2.html.


Land Nav 2 • Send Feedback • Land Nav 1 • © 2026 Gerry Shaw