Every component of the app on one page, one section each: what it is for, its variations and states, its properties and sizes. Scroll through them, or pick one in the menu; the menu marks the one on screen.
Source: web/shared/components, and the page parts in web/packages/crm/src where a component lives there.
Button
The app's one button. Primary is for the single main action on a screen. A disabled button always says what is missing.
Source: web/shared/components/Button/ (Button.tsx, useButton.ts for the disabled reason, Button.styles.ts, index.ts).
Variants
Four variants, one size.
primary
secondary
ghost
destructive
Variant
Look
Used for
primary
Filled primary-600, white text
The one main action of a screen or a form
secondary
White, gray-200 border and the card shadow, gray-900 text
Other actions: a card's actions, Search, the pager
ghost
No border or fill, primary-600 text
Cancel and other low-weight actions
destructive
Filled crit-text, white text
An action that closes or removes something, such as Mark as Lost
States
Hover darkens, keyboard focus draws a ring, disabled goes gray whatever the variant.
Primary
default
hover: primary-700
keyboard focus: 3 px focus ring, 2 px offset
disabled
Other variants on hover
secondary: gray-50
ghost: primary-100
destructive: 10% darker
Disabled with its reason
Needs Heads of Terms agreedthe reason sits under the button and is read out with it
Width
A button is as wide as its label. fullWidth stretches it to its container, as on the sign-in card.
Shown under a disabled button and tied to it with aria-describedby
type
default button
Pass submit inside a form
Size: 36 px high, 16 px side padding, 8 px radius, 8 px between an icon and the label, text 14 / 20 Medium, after Apiko's design concept.
Compared with the design board
The "Components: actions and inputs" board of the Design Foundations canvas. This section shows the app's version.
Size and weight. The board: 44 px high, 20 px side padding, Semibold, a gray-300 border on the secondary button. Since 2026-10-07 the button follows Apiko's design concept: 36 px, 16 px padding, Medium, a lighter border with the card shadow.
Icon button. The board has a 44 × 44 square button with a plus ("Add action"). The app has no such component: actions are added with an ordinary text button.
Text field
A labeled single-line input. The label sits above the field; a hint, an error or a lock line sits under it.
Source: web/shared/components/TextField/ (TextField.tsx with TextField and LockedHint, useTextField.ts for the ids, the descriptions and Show / Hide, TextField.styles.ts, index.ts); the text area is LabelledTextArea in web/packages/crm/src/pages/lead-detail/fields.tsx.
States
default
Hint text sits under the field.
focus: focus border, 3 px primary-300 ring
Enter the number of units as digits
error mark only: the form-level message says what is wrong
The same line also stands on its own beside a control a role cannot use: a stage move, a switch, a tab's buttons. Every locked thing names its owner the same way.
Set by Back Office. Read only for Land.
Land moves this deal on. Read only for Construction.
Read only for Admin.
Properties
Property
Values
What it does
label
text, required
The label above the field
hint
text
A gray line under the field
error
text or true
Text: a red line with an icon under the field. true: only the red border, for a form whose own message already says what is wrong
readOnly
boolean
Gray fill, not editable
readOnlyHint
text
The lock line under a read-only field; names the function that owns it
type
default text
password adds Show / Hide and turns off spellcheck and autocorrect
Sizes: label 12 / 16 Semibold gray-700; 6 px between label, field and the line under it; field 36 px high (the height of a button, after Apiko's design concept), 12 px side padding, 8 px radius, text 14 px; the Show / Hide button inside a password field is 28 px; an error is a 2 px crit-text border.
Compared with the design board
The "Components: actions and inputs" board of the Design Foundations canvas. This section shows the app's version.
Height. The board: 44 px. Since 2026-10-07 the field is 36 px, the height of a button, after Apiko's design concept; its border stays gray-300, so its edge doesn't fade further.
Search. On the board the search field has a magnifier icon inside. In the app it is a plain field with a "Search" button beside it.
Select
A list to choose one value from. Two forms: a compact filter that carries its name inside each option, and a field with a label above it. Since 2026-10-07 the app draws the list itself: a chevron that turns when the list is open, a white panel on the popover shadow, 36 px options with a check on the chosen one.
Source: web/shared/components/Dropdown/ (the control and its list, ADR-0040: Dropdown.tsx, the useDropdown hook, Dropdown.styles.ts); Select.tsx beside it (the filter); LabelledSelect in web/packages/crm/src/pages/lead-detail/fields.tsx (the labeled field, also the role on Create staff user).
Filter
Used above the overview tables. It has no label above it: each option reads "Region: All", "Region: Berkshire", so the chosen value explains itself. The name is also the control's accessible name.
default: "All" is the first option
a value chosen
disabled
a value from an old link that no option matches
The lists open as the app's dropdown: a 16 px chevron 12 px in from the edge that turns when the list is open, a white panel on the popover shadow with 36 px options and a check on the chosen one, above the control when there is more room there. Keyboard: arrows, Home, End, Page Up and Page Down move; typing finds an option by its own word ("b" steps through the regions starting with B); Enter and Space choose; Escape closes.
Labeled field
Used in forms. A placeholder option and a hint are optional.
Required before Instruction. Decides whether the Contractor stage applies.
Properties
Filter (Select)
Property
Values
What it does
label
text
The filter's name: the prefix of every option and the accessible name
value
text; empty is "All"
The chosen option
options
list of value and label
The choices after "All"
allLabel
text; default "All"
The wording of the first option
disabled
boolean
Gray, not usable
Labeled field (LabelledSelect)
Property
Values
What it does
label
text
The label above the field
options
list of value and label
The choices
placeholder
text
A first option with no value, such as "Select a deal type"
hint
text
A gray line under the field
Size: 36 px high (the height of a button and a field, after Apiko's design concept), 12 px side padding, 8 px radius, text 14 px; the filter is at least 160 px wide.
Compared with the design board
The "Leads Overview" board of the Design Foundations canvas.
Height. The board: 44 px. Since 2026-10-07 the control is 36 px, in line with the button and the text field, after Apiko's design concept.
The boards draw native selects. The app has opened its own list since 2026-10-07 (App changes item 1, now in the app); the boards still show the browser's arrow and leave the list to the system.
Date picker
A field that opens a calendar to choose one day. Every word in it is English, whatever language the browser is set to.
Proposed, not in the app yet: see App changes. In the app a date is a TextField with type="date", in three places: Due date in AddActionForm, Date in LogActivityForm and Tender due in ContractorRow.
The calendar
One month at a time, weeks starting on Monday. Shown open here; in a form it opens under its field, or above it when there is more room there.
Part
Look
Month title
"October 2026", 14 / 20 Semibold; two arrows step a month back and forward
Weekday row
Mo to Su, 12 / 16 Semibold gray-500
Day
36 px square, 8 px radius; gray-100 under the pointer
Today
A 1 px primary-600 ring, primary-700 Semibold
Chosen day
Filled primary-600, white Semibold
Day of a neighboring month
gray-500; still a day that can be chosen
Day that is not allowed
gray-300, not clickable
Clear, Today
Text buttons in primary-600; Clear empties the field, Today chooses today
Field states
The field is the same 36 px control as a text field, with the calendar icon at its right. Click a field to open its calendar.
a day chosen: "12 Oct 2026", the app's date format
empty: "Select a date" in gray-500
no later than today: later days are gray and cannot be chosen
disabled
Keyboard
Opening the calendar puts the focus on the chosen day, or on today when the field is empty.
Key
Does
Enter or Space on the field
Opens the calendar
← →
The day before, the day after
↑ ↓
The same weekday a week earlier, a week later
Home, End
Monday and Sunday of the week
Page Up, Page Down
The same day a month earlier, a month later
Enter or Space on a day
Chooses it and closes the calendar
Escape
Closes the calendar and returns to the field; the value stays
Tab
Moves through the month arrows, the days, Clear and Today; leaving the calendar closes it
Values and sizes
Property
Values
What it does
value
a day as YYYY-MM-DD, or empty
The chosen day; the same text the app's forms pass to a date field today
min, max
a day as YYYY-MM-DD
The earliest and latest day that can be chosen; an Activity's date has max set to today
disabled
boolean
Gray, does not open
Sizes: field 36 px high, 12 px side padding, 8 px radius, a 16 px calendar icon. Calendar: 12 px padding, 8 px radius, the popover shadow, seven 36 px columns 2 px apart, always six weeks so its height does not change from month to month; arrows and text buttons 32 px high.
Search form
A list's free-text search: a field and a Search button. It applies when the form is submitted, not on every keystroke, so typing does not send a query per letter.
Source: web/shared/components/SearchForm.tsx.
States
empty
a search applied
Behavior
The text is trimmed before it is applied, and is at most 100 characters.
The applied search lives in the page's URL, with the filters and the page number.
Clearing the filters empties the box.
Properties
Property
Values
What it does
label
text
The label above the field
value
text
The search that is applied now
onSearch
function
Called with the trimmed text on submit
Compared with the design board
The "Components: actions and inputs" board of the Design Foundations canvas. This section shows the app's version.
No button on the board. The board draws one search field with a magnifier inside; the app pairs a plain field with a Search button.
Switch
An on / off control. The app has one: "Land terms agreed" on the Opportunity page. It is written inside that card, not as a shared component.
The switch renders only for a user who may change the value. Everyone else sees the state as text, with a line that says why.
Land terms agreedSet by Land. Heads of Terms needs it.
Not agreed yet
Land terms agreedSet by Land. Heads of Terms needs it.
Agreed
Land terms agreed
Set by Land. Read only for Construction.
Agreed
Land terms agreedFixed from Legals on, and on a closed deal.
Agreed
State
Who sees it
Look
Off
The owning role, while the value can change
Track gray-300, knob on the left, "Not agreed yet" in gray-500
On
The owning role, while the value can change
Track primary-600, knob on the right, "Agreed" in good-text
Locked
Any other role
No switch; the lock line names the owner
Fixed
Everyone, once the deal is past the stages where it can change
No switch; a note says when it was fixed
Size: 44 × 24 px track, fully rounded; 20 px white knob with a soft shadow, 2 px in from either end. While a change is being saved the switch is disabled.
Compared with the design board
The "Opportunity: Overview" board of the Design Foundations canvas. This section shows the app's version.
Not on the foundations boards. The switch appears only on the Opportunity board.
Chip and tag
Small labels that say what kind of thing a record is. Stage, deal type and owning function are always separate things, so each has its own color, and a record's state (its stage, its deal type) carries a dot.
Source: web/shared/components/Chip/: Chip.tsx (the component, and the label maps for stages, deal types, account types and the other enums), Chip.styles.ts (the classes and the function colors), index.ts.
Stage
A blue pill: primary-100 with primary-700 text, a hairline border a shade darker than the fill, and a dot in the text's color. One per stage of a Site. In a table cell it is drawn plain, with no border or dot, as the concept draws it in its tables; that also keeps it inside the narrow Stage and Status columns.
Lead stages
New LeadContactedQualifiedAppraisal RequestedAppraisedFee DiscussionInstructed
Opportunity stages
BuyerContractorHeads of TermsLegalsExchange / CompletionClosed / Lost
In a table cell
Stage
Deal type
Heads of Terms
Land & Build
Owning function
A tinted label in the function's own color, with 8 px corners and no dot. It shows which function owns the current stage or an action.
Back OfficeLandPartnershipsConstructionLegal
Deal type and neutral tag
Deal type is a neutral pill with a dot, like a stage: gray-100 with gray-700 text and a gray-200 hairline, plain in a table cell. Any other neutral label (an account's type, a critical Legal task) is a plain gray pill, with no border and no dot.
Size: 24 px high, 10 px side padding, text 12 / 16 Medium; a 6 px dot, 6 px before the text. Stage, deal type and tag are fully rounded; a function has 8 px corners.
Compared with the design board
The "Components: actions and inputs" board of the Design Foundations canvas. This section shows the app's version.
The concept's chips. The board: Semibold pills, and the deal type as a square white tag with a gray border. Since 2026-10-07 the chips follow Apiko's design concept: Medium text; a stage and a deal type carry a dot and a hairline border (plain in a table cell); the deal type and any other tag are gray pills; a function has 8 px corners.
The "Buyer stage" chip. The board labels the stage "Buyer stage"; in the app the stage is called "Buyer".
Status pill
A pill with an icon that says how something is doing. Three tones, each with its own icon shape, so a status never depends on color alone.
Source: web/shared/components/StatusPill.tsx, StatusIcon.tsx; the Stalled badge is StallBadge in DealHealth.tsx.
Tones
Without text of its own, a pill shows its tone's default label.
On trackgood: a check in a circle
At riskwarn: a triangle
Stalledcrit: a cross in a square
Tone
Colors
Default label
Means
good
good-text on good-tint
On track
Deal Health 70 and above; a deadline further than a week away
warn
warn-text on warn-tint
At risk
Deal Health 40 to 69; a deadline this week; no next action
crit
crit-text on crit-tint
Stalled
Deal Health below 40; an overdue deadline; a stall rule fired
Custom text
Any text can replace the default label; the tone and icon stay.
A crit pill that names the first stall rule that fired; "+n" when more did. Hovering it lists them all. A deal that is not stalled has no badge.
One rule
Stalled: no contact 21 daysStalled: Heads of Terms open 34 daysStalled: tender overdueStalled: critical Legals task past deadline
More than one
Stalled: no contact 21 days+2
Icon
The status icon is also used on its own, in the color of the text around it.
Sizes
14 px: in pills and score chips
16 px: a password rule
18 px: a form message, a done action
24 px: the success mark
Properties
Property
Values
What it does
status
good, warn, crit
The tone: colors and icon
children
text
Replaces the default label
Size: 24 px high, fully rounded, 10 px side padding, 6 px between icon and text, text 12 / 16 Semibold.
Health score
The Deal Health Score: a number from 0 to 100 beside its band's icon, so it reads without color. Clicking it opens the breakdown that explains the number.
Source: web/shared/components/HealthChip/ (HealthChip.tsx with the bands, HealthChip.styles.ts, index.ts), DealHealth.tsx (HealthBreakdown).
Bands
The band decides the color and the icon. The thresholds live in one place on the web side.
Deal health 100Deal health 70Deal health 69Deal health 40Deal health 39Deal health 0
Band
Score
Tone
Green
70 to 100
good, check in a circle
Amber
40 to 69
warn, triangle
Red
0 to 39
crit, cross in a square
Display and button
On its own the score is a label. With a click handler it becomes a button with an "i" icon, darker under the pointer.
Display only
Deal health 72Deal health 45Deal health 31
Button
No score
—a deal in a final stage has no score
Breakdown
Every component with its points and the fact behind them, then the stall rules that fired. Click the score to collapse or expand the sample.
Deal health 75 (100 less each component; 70 and above)
Recent contact
1 day since the last contact; points off from 14 days
0
Overdue actions
1 open action past due
−10
Stage duration
12 days in this stage; no limit for this stage
0
Next action
4 open actions
0
Legals
1 critical task past deadline (counted in the Legals stage)
−15
Stall rules fired
critical Legals task past deadline
tender overdue
Proposed weights, not yet approved by Valere.
In the app the panel is 384 px wide and fixed to the viewport. It opens below the score, or above it when there is more room there, and closes on Escape, a click outside, a page scroll or when focus leaves it.
Properties
Score (HealthChip)
Property
Values
What it does
score
number
Rounded and kept within 0 to 100
onClick
function
Makes the score a button
expanded, controls
boolean, element id
Tell assistive technology whether the breakdown is open and which element it is
Breakdown (HealthBreakdown)
Property
Values
What it does
health
score and its components, or nothing
Nothing renders the dash
stallRules
list of fired rules
Shown under the components
Size: 24 px high, the height of a chip, fully rounded, 10 px side padding, a 1 px border a shade darker than the band's tint, text 12 / 16 Medium with tabular figures; the "i" of the button form is 12 px and a little quieter.
Compared with the design board
The "Components: actions and inputs" board of the Design Foundations canvas. This section shows the app's version.
Size. The board: 32 px, 14 px Semibold, no border. Since 2026-10-07 the score follows Apiko's design concept: 24 px, 12 px Medium, a hairline border in its band's tone. Its icon keeps the band's shape, and its number keeps the band's text color rather than the concept's paler one, so it still passes 4.5:1.
Breakdown content. The board: a 320 px panel with rows "Start 100", the deductions that fired, and "Score". The app: 384 px, always all five components with the fact under each, and a separate block of the stall rules that fired.
Shadow. The board gives the panel the popover shadow; the app gives it the card shadow.
Tabs
The tab bars of the Opportunity page, the Registers and the Morning Meeting Board, and the one-of-several filters (the Accounts Overview's type, a Contractor's Bid history outcome, the Activity card's type), all in one look. Each bar keeps its own behaviour: an Opportunity or Register tab is its own URL, a board view is a ?view in the URL, and an Account type is a filter button, so a tab can be linked to and the back button works.
Source: web/shared/components/TabBar/ (TabBar.tsx, the track, a nav or a role="group"; TabBar.styles.ts with tabClassName; index.ts). A route tab is web/packages/crm/src/components/TabLink.tsx, a NavLink; the bars are pages/opportunity-detail/OpportunityTabs.tsx, pages/registers/RegisterTabs.tsx, the views in pages/MorningMeeting.tsx, and the filters in pages/AccountsOverview.tsx, pages/account-detail/contractor/BidHistoryCard.tsx and pages/opportunity-detail/ActivityCard.tsx.
States
State
Look
Active
Raised on white with the card shadow, gray-900 text, Medium; in forced colors (Windows high contrast), where fills and shadows are dropped, also a 2 px outline
Inactive
gray-500 text, Medium, on the gray-100 track
Hover (Contractors in the sample)
gray-700 text, a step lighter than the open tab's
Tab sets
The set depends on the deal. Contractors exists only on Land & Build, once a buyer is Agreed or the Contractor stage is reached; on every other deal its URL lands on Overview. Activity & Risk is on every deal, and a closed deal adds Closed.
Land & Build with an agreed buyer: six tabs
Any other deal: five tabs
Size: the track is gray-100 with 4 px padding, a 12 px radius and 4 px between tabs; a tab is 32 px high with 12 px side padding, an 8 px radius and text 14 / 20 Medium. The Opportunity page's bar spans its row and, from 1024 px on, spreads its tabs; the others size to their tabs. In the app the tabs are links (filter buttons on Accounts); here they are buttons, so the samples switch in place.
Every tab has the same weight, so no tab moves when the active one changes. The hidden Semibold copy of each label that TabLink held for that (the design review's fix of 2026-10-07) went with the concept's tabs.
Compared with the design board
The "Components: actions and inputs" board of the Design Foundations canvas. This section shows the app's version.
Inactive tab. The board colors it gray-700; the code uses gray-500, darker only on hover.
The concept's tabs. The board: underlined tabs, the active one blue and Semibold, on a white strip with rounded top corners. Since 2026-10-07 the tabs follow Apiko's design concept: a gray track with the active tab raised on white, one weight for every tab, and the Opportunity page's bar on its own row under the header card.
Form messages
What a form says back: one error or success line for the whole form, a live checklist under a new password, and the success mark of a finished step.
A tinted bar with the status icon. An error is announced at once (role="alert"), a success politely (role="status"). With no message, nothing renders.
Wrong email or password.
This reset link is invalid or has expired.
Profile saved.
We sent another link.
Password rules
A rule turns green with a check once it holds, and shows a dash until then.
8 to 72 characters
Both passwords match
8 to 72 characters
Both passwords match
8 to 72 characters
Both passwords match
Success mark
A 48 px round check above the title of a completed step, such as "Check your email".
Sizes
Message bar: 12 px vertical and 14 px side padding, 8 px radius, 10 px between the 18 px icon and the text, text 14 / 20. Rule: 16 px icon, 8 px gap, text 14 / 20.
Card
A white panel with a title bar. Pages group their fields and tables in cards.
Source: web/shared/components/Card.tsx; a label over a value is Fact in web/packages/crm/src/pages/lead-detail/Fact.tsx.
A label over a value, laid out in a grid inside a card. The value is Medium. In a record's header (Lead, Opportunity, Account, Contact) the facts sit in one row with no rule above them and a thin rule between them, as in the design concept.
Region
Berkshire
Units
60
Deal type
Land & Build
Owner
Liam Carter
In a header
Deal owner
Liam Carter
Landowner
R. & J. Whitmore
Region
Berkshire
Probability
60%
Fee
£48,000
Days in stage
48
Properties
Property
Values
What it does
title
text
The card's heading
actions
any content
Sits at the right of the title bar: usually one secondary button
children
any content
The body
Sizes: 12 px radius, 1 px gray-200 border, the card shadow; title bar 56 px high with 20 px side padding and a line under it; title 16 / 24 Medium; body padding 20 px.
Compared with the design board
The "Type, spacing and shape" board of the Design Foundations canvas. This section shows the app's version.
Header facts. The boards put a line above a record's facts and set each value in Regular. Since 2026-10-07 they follow Apiko's design concept: no line above, a thin rule between the facts, values in Medium.
Padding. The board: 16 px, 24 px for large cards. In the code the card has 20 px.
Stat tile
A total above an overview table: an upper-case label and a large figure.
Source: Tile in web/packages/crm/src/pages/overview-parts.tsx.
Variations
Total sites
6
Active
5
Not exchanged, completed or lost
Weighted pipeline fee
£412,500
Instructed
–
Variation
When
Figure
The usual tile: a count
Figure with a note
When the label alone does not say what is counted
Money
The Weighted Pipeline Fee on the Opportunities Overview
Dash
While the figure is loading
Properties
Property
Values
What it does
label
text
Upper-case label
value
any content, or nothing
The figure; nothing shows a gray dash
note
text
A small gray line under the figure
Sizes: 20 px padding, 12 px radius, the card shadow; label 13 / 20 Regular in sentence case; figure 26 / 32 Medium with tabular figures. Tiles sit in a grid with 16 px gaps: two across, four on a wide screen.
List table
The table of an overview page and the smaller tables inside cards. A record's name is always the link to its one record.
Source: web/packages/crm/src/pages/LeadsOverview.tsx and the other overviews; cell classes in pages/overview-parts.tsx; the table inside a card is RecordTable in pages/account-detail/table.tsx.
Overview table
The Stalled badge sits under the name, where it has room to name the rule. The score is a button that opens its breakdown. Numbers are right-aligned with tabular figures. On a narrow screen the table scrolls sideways inside its frame.
The "Components: data display" board of the Design Foundations canvas. This section shows the app's version.
Owner column. The board puts a function chip in the table. The app writes the deal owner's name, or "Unassigned".
Row height. The board: 56 px. The code sets no row height: the content and the cell padding decide it.
Legal task status. The board: a colored icon and a label ("Completed"). The app: plain text ("Complete"), and the critical-path mark is a "Critical" tag instead of a "Critical path" chip.
Pager
The footer of a paged table: how many rows are shown out of how many, and Previous / Next.
Source: web/shared/components/Pager.tsx.
States
Where the page number lives is the page's business (the URL, on every overview); the pager only shows it and asks for another.
First page
Showing 1–50 of 112 sites
A middle page
Showing 51–100 of 112 sites
Last page
Showing 101–112 of 112 sites
No rows
No sites
Loading: no text, both buttons disabled
Properties
Property
Values
What it does
page
number, from 1
The page shown
pageSize
number
Rows per page: 50 on the overviews
total
number
All rows that match
noun
text
What the rows are: "sites", "opportunities", "accounts", "contacts"
isLoading
boolean
Empties the text and disables both buttons
onPage
function
Called with the page to go to
Size: 56 px high, 16 px side padding, a 1 px gray-200 line above it, text 14 px gray-500; two secondary buttons 8 px apart. A change of the text is announced politely.
Stage bar
Where a Site is along its stages: a segment per stage, its name under it, after Apiko's design concept.
Source: web/packages/crm/src/pages/lead-detail/StageBar.tsx; the stage counts are in opportunity-detail/BuyersTab.tsx and ContractorsTab.tsx.
Positions
Done stages are blue, the current one is half blue on a light blue with its name in blue, upcoming ones are gray.
At the first stage
New Lead (current)
Contacted
Qualified
Appraisal Requested
Appraised
Fee Discussion
Instructed
Part way
New Lead (done)
Contacted (done)
Qualified (done)
Appraisal Requested (done)
Appraised (done)
Fee Discussion (current)
Instructed
Past the bar: a Site that has moved beyond these stages shows them all done
New Lead (done)
Contacted (done)
Qualified (done)
Appraisal Requested (done)
Appraised (done)
Fee Discussion (done)
Instructed (done)
Step
Segment
Label
Done
primary-600
gray-900
Current
Half primary-600 on primary-100
primary-600, Medium
Upcoming
gray-200
gray-500
Stage counts
On the Buyers and Contractors tabs many records move through the stages at once, so the bar becomes a row of columns: how many are at each stage, each column as tall as its share of the busiest stage, after Apiko's design concept. A stage that holds any is blue; an empty one keeps a gray baseline.
Buyers tab
Buyer stage
Owner:Partnerships
2
Coverage
1
Interested
1
Offer received
0
Agreed
1 withdrawn
Contractors tab
Contractor stage
Owner:Construction
3
Invited
0
Tender received
0
Shortlisted
0
Appointed
Properties
Property
Values
What it does
stages
list of stages, in bar order
Comes from the API's stage table, not from the component
current
a stage
A stage that is not in the list marks every step done
label
text; default "Lead stage"
The bar's accessible name
Sizes: 6 px segments, fully rounded, 6 px apart; 8 px between segment and label; label 12 / 16, left-aligned; the steps share the width equally. Screen readers hear each stage with "(done)" or "(current)".
Compared with the design board
The "Components: data display" board of the Design Foundations canvas. This section shows the app's version.
The concept's segments. The board draws a dot per stage joined by a line, the current one a white circle with a 3 px ring. Since 2026-10-07 the bar follows Apiko's design concept: a segment per stage, the current one half filled, the labels left-aligned. The concept's "Done · 41 days" and "Signed 1 Sep" under each stage need a stage history the app doesn't keep, so they aren't shown.
Buyer journey. The board draws seven steps (Coverage, Teaser, Pitched, Interested, Offered, Board, Agreed) as the same bar. The app has four stages plus Withdrawn, and shows them as counts.
The "Contractor stage not applicable" note. It is on the board; the app simply does not show the Contractors tab on such deals.
Deal clock
The three legal dates of a deal, each with a countdown: target exchange, target completion and longstop. Read only.
Source: web/packages/crm/src/pages/opportunity-detail/LegalsTab.tsx; the countdown wording is countdown in pages/lead-detail/format.ts.
The card
Deal clock
Target exchangeTarget
42 days
16 Nov 2026
Target completionTarget
70 days
14 Dec 2026
LongstopHard date
105 days
18 Jan 2027
States of a date
The countdown is worked out in the browser from today's date, in whole calendar days.
AheadTarget
42 days
16 Nov 2026
TomorrowTarget
1 day
7 Oct 2026
TodayTarget
Today
6 Oct 2026
PassedTarget
3 days ago
3 Oct 2026
No dateHard date
Not set
Key dates, on the Overview
The same three dates on the Opportunity's Overview tab (opportunity-detail/KeyDatesCard.tsx): a row per date, its countdown as a badge after it, the targets' in blue and the longstop's, a hard date, in gray, as in Apiko's design concept. The wording is the Deal clock's.
Key dates
Target exchange
23 Nov 202647 days
Target completion
21 Dec 202675 days
Longstop
25 Jan 2027110 days
Sizes
Each date is a tile with a 1 px gray-200 border, 12 px radius and 16 px padding; the label 14 / 20 gray-500 with its tag at the right (Target in blue, Hard date in the critical tone, 20 px); the countdown 24 / 32 Medium with tabular figures; the date 14 / 20 gray-500 under it. Three tiles in a row, 16 px apart; one column on a narrow screen.
Compared with the design board
The "Components: data display" board of the Design Foundations canvas. This section shows the app's version.
Layout. The board: rows with a large day count (32 px) at the right. Since 2026-10-07 the app follows Apiko's design concept: three tiles, each led by its countdown with the date under it, and a Target or Hard date tag. The concept's progress bar and its notes ("5 critical items open", "subject to PC") need data the app doesn't hold per date, so they aren't shown.
Action list
A deal's Next Actions: who owns each, what it is and when it is due. One deal can hold several open actions at once, across functions.
LandChase landowner for Heads of Terms signatureOverdue 2 days
LegalConfirm draft contract with seller’s solicitorToday
PartnershipsSend teaser to two registered providersThu 8 Oct
Back OfficeSend the first approach emailDone 26 Sep
States of a row
State
Mark
Title
Due
Overdue
Blue ring
Normal
crit-text, Medium: "Overdue 2 days"
Due today
Blue ring
Normal
gray-500: "Today"
Due later
Blue ring
Normal
gray-500: the day and date, "Thu 8 Oct"
Done
Green check
gray-500
good-text, Medium: "Done 26 Sep"
An open action also has a button to complete it, at the right of its row; the admin, who is read-only, sees a lock line instead. An empty list reads "No actions yet."
Sizes
A row is a grid: 28 px for the mark, 130 px for the function chip, the title, then the due state; 12 px gaps and padding, a 1 px gray-100 line between rows, text 14 / 20. Since 2026-10-07 the open mark is a primary-600 ring and the due states are Medium, after Apiko's design concept; the concept's dashed line under the mark isn't drawn.
App shell
The frame of every signed-in page, after Apiko's design concept: a light gray sidebar with the brand, the signed-in user and the menu in groups, beside a white page. The sidebar folds to a rail of icons and back, and remembers the choice.
Source: web/shared/components/Sidebar/ and web/shared/components/AppLayout/ (each a folder: the component, its .styles.ts and index.ts, and the Sidebar's useSidebar hook for the fold; the AppLayout has no logic, so no hook); in the app, components/AppShell.tsx (the menu, the user, the router links) and UserAvatar.tsx.
Sidebar
Open: 240 px on gray-50, a line at its right. The brand and the fold button, the user card, then the menu under three captions: Workspace, Pipeline, Directory (an admin also sees Admin, with Create staff user). Folded: a 64 px rail of icons, a rule for each caption, the avatar alone; each icon shows its name on hover and on keyboard focus.
Open and folded
Menu item
Look
Default
gray-600 text, Regular
Hover (Opportunities in the sample)
gray-100 fill, gray-900 text
Current page (Leads in the sample)
gray-200 fill, gray-900 text, Medium
Folded
40 px square, the icon alone; a gray-900 tooltip with the name
A menu item is 40 px high with an 8 px radius, 12 px side padding, an 18 px icon and 12 px between icon and label; items are 2 px apart, groups 16 px. The fold button is labeled "Collapse sidebar" or "Expand sidebar" and tells screen readers whether the sidebar is open; the choice is kept in localStorage, and a browser that blocks storage still folds, for that page only. The user card is one link to My account; its label starts with the visible name.
Breadcrumbs
On a record page, above its header: the Overview as a link, a chevron, the record in ink, at 14 px. The Lead, Opportunity, Account and Contact pages each draw their own.
Record page
Avatar
The user's initials in the function color of their role. MD and the admin use the Legal slate. Until the account has loaded, nothing is drawn.
By role
BMBack Office
LCLand
STPartnerships
DHConstruction
ARMD, admin
Sizes
LC32 px rounded square, in the sidebar
LClg: 48 px circle, on My account
Page area
The page sits on white with 24 px padding; its blocks are 16 px apart. The shell sets no maximum width.
Compared with the design board
The Design Foundations canvas's page boards. This section shows the app's version.
The concept's shell. Since 2026-10-07 the shell follows Apiko's design concept, not the boards: a gray sidebar with the user at its top and grouped items (the boards: a white sidebar, the user at the right of a top bar), the current page on gray (the boards: primary-100), a white page (the boards: gray-50), and a sidebar that folds.
Not taken from the concept. Its top bar (breadcrumbs, page actions, search, the "Updated" time, team avatars, Share and Inbox) and its item counts and Activity Feed, Regional, Capacity and Settings items: the bar would need the pages to hand it their content, a change of how the app is built, and the rest needs data or features the app doesn't have. The breadcrumbs stay above the page header.
Content width. The board: fluid, at most 1440 px. The shell sets no width limit.