Changelog
Plain-English history of design system changes, with before/after captures.
Every change to the tokens or components gets an entry here: what changed, in human terms, and what it looks like. Newest first.
6 October 2026
Section card headers fit narrow cards
Card headers now adapt to the card's width instead of the screen's. A new View Switch shows a Segmented Control when there is room and a chip Select when there is not, so a view toggle never pushes the title off the card. Below 32rem the detail label also drops, leaving the chevron. The same card now behaves the same on a phone and in a narrow sidebar. Rules: Responsive Behaviour.
Crisper modal edge, firmer field border
Dialogs in light mode drop their soft drop shadow: over the dimmed backdrop
it read as a smudge, so the hairline alone now draws the edge. Field borders
in light mode step up from --border to --border-strong
(--field-ring). Dark mode is unchanged.
Password reset asks for the email first
Forgot password on sign-in opens a screen to check or change the email, then sends the reset link. The sent screen is unchanged.
5 October 2026
Four heading levels
Headings follow one ramp, all Medium: xl 24px for page titles, lg 20px
for drawer titles, md 17px for big section card titles (Holdings,
Performance), sm 15px for small section card titles (Returns, Valuation).
heading-lg is now the new 20px step: the old 24px heading-lg is
heading-xl, and heading-sm is new. Heading sizes keep their pixel
values except lg, which moves from 24px to 20px; use xl for 24px. Visible
changes: page titles go from 24px regular to 24px medium; drawer titles go
from 18px to 20px; small section card titles use the heading token instead
of body text (same 15px).
Display is now Figure
The display ramp is renamed to what it holds: big numbers. text-display-*
becomes text-figure-* (24, 32 and 42px, unchanged), and the Display
component becomes Figure. Words never use it: the sign-in, register,
email-link and password-reset headlines move from 32px regular to the
24px medium page heading (heading-xl). The font-display alias is gone;
everything uses font-sans, since Aktiv Grotesk is the only face.
Frost is more opaque
The glass fill behind menus, popovers and the nav is 64% opaque in both themes, up from 55% (light) and 56% (dark).
Colour tokens are roles
Portal colours now come from one small set of roles: surfaces, ink, edges, states, status (fill and ink per status) and numbers. Brand colours are flat primitives: ivory, soil, deep grove, limestone, deep terracotta. Visible changes: the logo plate and checked checkboxes and radios turn deep grove; destructive actions and error text are a step darker red; gains and losses darken; secondary text darkens slightly; the primary button uses soil; the secondary button is a solid grey with the hover wash layered on top. The drawer mint wash is gone.
What's inside weight rows stack on phones
On phone widths, each weight row (top holdings, sectors, countries, asset
classes) puts the company or label on the first line and the bar with its
percent on the next. From sm (640px) up they stay on one line.
Secondary buttons are solid
Button secondary fill is the same grey wash, painted solid so content behind the control cannot show through. Hover uses the same treatment. Badges and rows still use the translucent secondary wash.
4 October 2026
Avatar rim is optional
Avatar border paints a 1px inset rim. It is off
by default. Country Flag and
Currency pass it through — cash flags have no
ring unless you set border.
Currency pairs the flag with the code
Currency is the labeled currency row: optional
flag (an Avatar) plus the code or a custom name, always vertically centered.
size is the same scale as Avatar. showFlag={false} is text only.
label replaces the text without changing the flag’s code.
Frost is menus and nav only
Dropdowns frost a bit denser (72% fill, 24px blur). The nav dock still
frosts. Drawers are solid --background in light and dark — no blur.
Edges are 1px, not a hairline
Layout, fields, cards, and inset rings share 1px. The retina 0.5px
--hairline-width override is gone. --border-width is the token;
--hairline-width still resolves to it so older bindings keep working.
Dark primary buttons step up to 900
On dark, the primary fill was --neutral-800. Rest is now --neutral-900;
hover is --white-fixed.
Table rows hover and stay selected
Clickable table rows wash with --hover. The open drawer’s instrument keeps
--selected (and a 2px --selected-bar) even if you hover another row —
one click switches. Closing the drawer fades the fill; the bar does not
animate. Keyboard focus is the field ring, inset on the row.
Wordmark follows the page ink
The sign-in lockup was a black SVG in an <img>, so it stayed black on dark.
It now paints with --foreground.
Questionnaire choices use the choice tokens
Questionnaire choice cards bind --choice-bg*
roles. Rest is neutral-100 on light and neutral-200 on dark, with no
outline. Hover steps one shade; checked stays that shade plus
--outer-border-strong. Keyboard focus is the field ring and halo — it
replaces the checked edge while the card is focused. Radios stay a 1.5px
neutral-500 ring, Soil fill on light, neutral-900 on dark.
Menus keep frost; the glass switches are gone
Account menu, Select, Combobox, and the other floating lists still frost —
translucent fill and 20px blur, captured from the profile menu. That paint
lives on --popover / --popover-blur in one shared menu surface. The
page-wide glass flag, surface groups, and Glass 2.0 recipe file are gone.
The dock, cards, and drawers stay solid.
3 October 2026
Dark fields sit above the card
On dark, a field used a fixed neutral-200 fill, so it was one step up on a
card and two on the page. Rest fill is now 4% white, always one notch above
its parent. Focus uses the border-strong step, not a text step. Disabled
sinks to the parent; locked / read-only keeps the rest fill with muted
value text. Light is unchanged.
Control edges sit above layout edges
Cards, panels, and dividers keep the quiet layout hairline (--outer-border
at 9% light / 5% dark). Fields, selects, outline buttons, and choice cards
share --control-border — Soil at 16% light, white at 10% dark — so a
control still reads as something you can act on. Hover steps to 24% / 16%.
Light outline buttons leave opaque neutral-300 for the same wash as the
field.
Control metrics follow the spacing scale
Field, input, and button padding are --spacing steps times --density
(default 1). Heights are the text line plus vertical padding, so a type or
padding change cannot leave the label off-centre. Condensed is --density: 0.8, not a second set of lengths. Input is 4.5 / 3; field is 4.5 / 3.5 /
1.5; button md is 3.5 wide at 15/22 (40px); lg is 4.5 at 16/24 (48px).
Choice controls sit on the label line
Radio and checkbox in a Field row align to the label’s first line, not the
whole content block. The gap between control and copy is 12px
(--choice-gap) in both the radio group and choice cards. Both controls
share --control-size and a 1.5px inset ring, so a radio no longer paints
thinner than a checkbox.
Dark choice cards use fill, not a white ring
On dark, a checked radio or checkbox Field used --primary (near-white) for
the card edge, while the control stayed brand-green and disappeared on the
surface. Checked is now a step-up fill (neutral-300) and a light control
(Soil neutral-900 with a dark mark). Unchecked choice cards are fill-only.
Keyboard focus still uses the field edge. Unchecked hover is fill-only
(neutral-100 light, neutral-300 dark). Light still uses brand-green
controls.
2 October 2026
Progress Bar — tiny fills stay visible
Progress Bar floors any non-zero fill to
the bar height (min-w-2 on an h-2 track). Shares like 0.01% used to
collapse under rounded-full; they now read as a round chip from the
left. Zero still shows track only.
Dark controls sit above the card
Chips, segmented tracks, and unselected risk pills use #142424 in dark.
Neutral-100 is the card, so that step could not show a fill. Light still
uses neutral-100.
1 October 2026
Text input — fill vs edge
Field cards follow fill encodes availability, edge encodes interaction.
Rest and filled keep --card with --outer-border. Hover only strengthens
the edge (--outer-border-strong) — no fill wash. Focus uses ink edge + 8%
ink halo. Error / success reuse the same 1px + 3px halo geometry (red-500 /
green-500 at 14%, 24% when focused). Disabled and locked share the muted
fill (neutral-100 + --inner-border); locked adds FieldLockedValue with
an unlock control, disabled cannot be unlocked. Message ink is red-600 /
green-600.
Error / red ramp from Figma
--red-* (and --error-* aliases) match Base System error for light
100 / 200 / 300 / 700 / 800. Light 400 / 500 / 600 are derived between the
wash and text solids. Dark red is a full authored scale: 100 #311212 …
800 #f3bfbd, with instrument chart negative at #dd696b (not --red-500).
Dark field error edge/halo uses 500 (halo at 18%); negative text and badges
use 600.
Section Card / Divider — no surface
SectionCard, SplitCard, and Divider no longer take surface.
SectionCardSurfaceProvider is gone. Card fill and edge follow the shared
layout tokens; drawers no longer wrap children to force a white surface.
Layout border set (outer / inner / strong)
Layout edges collapse to three alpha roles plus one width:
| Token | Light | Dark |
|---|---|---|
--outer-border | Soil neutral-800 @ 9% | white @ 5% |
--outer-border-strong | Soil @ 17% | white @ 9% |
--inner-border | Soil @ 6% | white @ 3% |
--hairline-width | 1px; 0.5px at ≥2dppx | same |
--border aliases --outer-border. Cards use --card-hairline (inset
ring); fields and tables use CSS border / bg-border-inner. Deprecated
aliases (--border-card, --border-primary, --border-card-hover,
--card-ring-width) remain until 2026-11-01. Dark modal edge tracks
--outer-border-strong (no solid neutral-300). Sidebar and Divider
use --inner-border. Active chart range uses --outer-border-strong.
Section Card API
SectionCard header slots are now title, count, byline, actions, and
detail (href or onOpen). layout is inset or flush. size is md
or sm (no responsive sm-lg). Whole-header navigation uses a stretched
detail control; actions sit outside its hit area. Dropped: onClick,
titleAccessory, wrapBody / flushBody, titleRowClassName, and
headerless interactive cards.
Home Holdings and Recent activity both use Section Card + flush table.
One layout border
Light and dark share one outer layout hairline for card edges and
controls (--border / --border-card). Separators later split onto a
lighter --inner-border — see Outer and inner layout borders.
Checkbox / radio and focus rings stay separate. Interactive tiles may still
intensify via --border-card-hover.
Softer light card edges
Light surface edges (cards, fields, charts, menus) use a black-alpha hairline
instead of opaque neutral-200, matching the dark alpha model. Elevation
shadows use the same transparent-dark-* family so lift and hairline match.
Valuation scrub chip shows contributions
When contributions are on, the Chart scrub chip lists the contributions amount under the portfolio value.
30 September 2026
Modal layers
A modal sits on its own surface. In light that is white, the same as a card, over an ivory page, with a black overlay at 40%. In dark the page, card, modal, blocks inside the modal, and menus each take the next neutral step, and the overlay is black at 60%. The dark modal edge is one step above its surface, with a faint inner highlight on the top and left. The shadow is the menu shadow in light, and a larger darker cast in dark. Nested cards inside a modal have no border. The close control is a ghost button. The corner radius matches the card.
Key value emphasis
Key Value can put the medium weight on the
label. emphasis="key" mutes the value. The default still mutes the label
and keeps the value medium.
The washed palette is the default
Light and dark now use the consolidated palette. Neutrals are warm. Info is blue, warning is amber, error is red, and success is green. Signals and the danger button use step 500. Badges are a wash with step 700 text and no border. Hover, selected, and secondary mix neutral 800, so they pick up the ramp's warmth.
Button type matches Figma
Button keeps the three Figma sizes: large 44px,
medium 36px, small 28px. Large and small use 16px type on a 21px line
with -0.31px tracking. Medium uses 15px type on a 22px line with
-0.23px tracking. The icon is 16px on all three.
Set B colors follow the ramps
Under Alternative (data-set="B"), the semantic scales follow the washed
ramps in light and dark. Info is blue, warning is amber, error is red,
and success is green, steps 100–800. Danger stays error 700, so it
follows red. Set A keeps its own info, warning, error, and success scales.
Alert title
Alert titles are semibold, with twice the space between the title and the description.
Warning alert
Alert has a warning variant: warning wash
behind the message, warning text on top.
Questionnaire
Questionnaire asks one question at a time.
Choices are radios, or checkboxes when multiple is set. Required questions
stay put until they are answered.
Alert
Alert is a short status message: title,
description, optional icon, and an action in the corner. default sits
on the card fill. destructive uses the error color.
Set B warning uses amber
Under Alternative (data-set="B"), the semantic colors follow the washed
ramps, in light and dark. Info is blue, warning is amber, error is red,
and success is green. Danger has no ramp: it stays on error 700, so it
follows red there too. Set A keeps its own scales. See
Colors.
Toast
Toast is a short message that appears after an action, then dismisses itself. Mount one toaster and call it when the result does not need a decision. Compare corner radius and fill on the Toast demo.
Folder style tabs
Folder Style Tabs is a pair of
overlapping tabs. The selected tab is transparent; the other sits recessed
on the card. Shell washes the whole card. Plain has no wash. Tab
band fades the color out at the bottom of the tabs so the body stays on
the card fill. recess (0–1, default 0.35) sets how far the idle tab
steps back. recess={false} keeps the folder fade and corner at an even 50/50 split so switching does not push the bar. rim turns the specular edge off. shadow (0–1) insets the idle tab along the bottom and inner edge.
Confirm Dialog
Confirm Dialog is a yes/no modal: title, description, Cancel and Confirm — no close (x) button. Prefer Dialog when you need a body or form fields.
29 September 2026
Key Value list API
Key Value is a list wrapper: KeyValue owns
density and separators and renders an Item List internally.
KeyValueRow is one muted-label / medium-value row. Do not nest
KeyValue inside another Item List — put rows inside KeyValue.
Key Value borderless
Key Value variant is divided (default
hairline between rows) or borderless (no separators). Pair borderless
with density="condensed" for tight stacks such as the holding drawer.
condensed vertical padding is py-1.5. On a flushBody Split Card
panel, the first Key Value drops top padding and the last drops bottom
padding (panel pt-card / pb-card stay). Or set flushTop /
flushBottom explicitly.
Split Card
Split Card stacks titled panels in one Card
shell with a flush Divider between each. Panel
layout matches Section Card title-inside (flushBody for Key Value lists).
An optional footer matches a normal Card action strip.
Divider
Divider matches the Card inset hairline:
--hairline-width thick, painted with --inner-border.
Section Card title sizes md sm
Section Card titleSize is md (Holdings,
default), sm (Cash / compact), or sm-lg (Cash/Performance responsive).
Heading adds matching sm. Card titles in the
holding drawer use sm; the dock title stays md.
Heading and Display
Heading (md / lg) and
Figure (sm / md / lg) wrap the type
scale from Typography. Use Heading for
section titles above or inside a card; Display for hero figures.
Bank icons load from the logo CDN
Bank Icon loads
https://cdn.usesophic.com/logos/{bankCode}.png. A missing file falls back
to the generic bank glyph.
Key Value loading rows
Key Value loading uses a label, help line, and value — the same columns as a loaded row, including an avatar when the row has one.
Dialog padding and widths
Dialog padding steps up, and the gap under the
body matches the gap under the heading. width is default (600px) or
narrow (450px).
Dialog backdrop and shadow
Dialog uses the Figma scrim, transparent/dark/12,
and a shadow of 0 10px 60px at 10% black.
Dialogs sit on white
Dialog panels use the card surface. On the ivory page that is white, instead of the page background.
Dialog footer border
Dialog footer takes bordered. It stays on, so
existing footers keep the hairline above the actions. Set it to false to drop
that line.
Flags and the brand icon use avatar sizes
Country Flag renders an
Avatar and loads the flag into it.
Brand Icon keeps its rounded-square frame and
takes the same xs through xl sizes. Favicon files stay on their own pixel
exports.
Avatar sizes follow a 4px ramp
Avatar sizes are 16, 24, 32, 48, and 64px. Medium and extra large stay 32 and 64. Extra small is no longer 12px, and large is no longer 58px.
Key Value row density
Key Value takes density: condensed,
default, or relaxed. relaxed is the roomier row. The default is
default.
Large buttons are pills
Button size="lg" uses the pill radius from the button design. The place-order morph uses that size, so Buy, Cancel, Confirm, and Done match it. The expanded panel around them keeps the card corner.
Alternative dark controls sit above the card
On the alternative dark palette, chips, segmented tracks, and unselected risk pills use #142424. Neutral-100 is the card there, so it could not show a fill. The default palette still uses neutral-100.
Tall input matches the large button
Input variant="tall" is the same height as
Button size="lg" (44px). The default input
stays 40px. NumberInput variant="tall"
is that same 44px row inside a Field.
Date Picker can block days
Date Picker takes an optional disabledDays prop, for example
{ before: tomorrow }. Blocked days show greyed out and can't be picked.
Without the prop, nothing changes.
28 September 2026
New Key Value
Key Value is a row with a muted label and a
medium value. emphasizeLabel makes the label medium and the same color as
the value. The value can be text or a component. An optional Edit link
with a chevron sits under the value. The label takes the leftover space, or a
fixed width with the value filling the rest.
Section Card heading can take a byline
Section Card takes an optional byline:
muted text at the top right of the heading. titleAccessory stays the slot
for a control.
26 September 2026
Alternative neutrals shift a step
Set B neutrals 50–800 are a step cooler and slightly darker. Neutral 0, 600, and 900 stay the same. The default palette is unchanged.
Alternative progress bars use a brand track
On the alternative palette, progress fill stays brand-400 and the remainder is brand-400 mixed 18% into the card. The default palette is unchanged.
Alternative palette is a step lighter
Set B ivory, neutrals 50–500, and brand 50/100 are slightly lighter. The default palette is unchanged.
Limit price no longer compares to market
The limit price field no longer shows a chip such as “0.5% under market”.
Amount Field no longer includes
limitVsMarketBadge.
25 September 2026
Info Callout
Info Callout is an info icon that opens a short modal: a title and a description. Use it when a label needs a sentence or two. A Tooltip stays a hover label.
24 September 2026
Dialog body lines up with the title
Dialog body text uses the same left inset as the title. The close X sits on the title line.
Section shadow
Section cards and chart panels carry a very light shadow around the frame. The inset hairline stays.
Table row hover
Hovering a table row or a list row fills it with neutral-100. Menus keep their own hover.
Segmented control track
The segmented control track is neutral-100, one step down from neutral-200. The selected tab keeps a very light shadow underneath. Chart range uses the same track and shadow.
Display small is 24px
text-display-sm is 24px. Line height stays 1.15.
Price guide labels
The chart’s low and high stay on the series: a short tick and the figure just above the peak, and just below the trough. They are there at rest and fade out while the pointer is on the chart. Those figures are one step larger than the date labels under the chart, and they use the main text color. The dates under the chart are the smaller size. The side wash on the plot is shorter and lighter. The range labels sit closer to the divider, and the selected underline stays on that line.
Shared color roles
Borders, chips, and the secondary button no longer share one grey. Section frames and inner rules use neutral-100. Nav pills, avatars, and count badges use a chip fill at neutral-200. Secondary buttons are a 6% wash of the text color. Focus rings, disabled button edges, and the nav shell ring use that same neutral-100 border.
Neutrals are one warm ramp in both palettes. The page background stays ivory, a step apart from the UI greys.
Hover is one treatment everywhere it appears: 4% of the text color on nav, menus, tables, lists, and ghost buttons. A selected row is 8%.
Status badges use the washed blue, amber, and red ramps. Change arrows, the danger button, and form errors stay on the saturated status colors. Past-performance bars use washed blue and red. Maturity and merger marks use washed green and magenta.
Placeholders are neutral-500. Field labels stay neutral-700. Currency symbols and decimals stay neutral-500.
Set B is a second palette on the same token names. Default stays set A. Outline badges keep their hairline in both palettes. Tab pills, range pills, and the earnings amounts do not.
In set B, the disabled button fill is neutral-200 and segmented tracks
(tabs and chart range) are neutral-100.
Choosing Alternative in Colours sets data-set="B".
Badge borders stay on outline only
Filled and tinted badges no longer draw a hairline. The outline badge keeps its border. Badges are also a little roomier: 24px tall, with 12px of side padding.
See Badge.
Small text is 14px
text-sm is 14px. It was 13px. Line height stays 1.4.
Avatar xs and xl
Avatars gain two sizes on the existing ladder. xs is 12px, one step under small. xl is 64px, one step over large. Small (16px), medium (32px), and large (58px) are unchanged.
See Avatar.
23 September 2026
Cash and performance titles on small screens
Home Cash and Performance use heading Medium 18/24 while those
cards sit under the chart (below lg). From lg up they stay the
smaller 15px sidebar title. Asset allocation stays 15px at every width.
See Section Card.
Tighter tabular figures
Tabular numbers sit a little closer together (-0.015em), including chart
headlines. Proportional text is unchanged.
Brand face: Aktiv Grotesk
Portal and docs now load Aktiv Grotesk (Regular + Medium) from
@sophic/fonts for all UI type. --font-display, --font-sans,
and --font-mono alias to --font-sans. --font-helvetica-text is
removed.
Tracking and leading were retuned for Aktiv: body near normal,
headings/display slightly tightened (-0.01em … -0.03em). Old SF-era
+0.5px / -0.23px tracking is gone. Product UI uses scale utilities
(text-sm / text-base / text-heading-* / text-display-*) — no
hardcoded text-[Npx] or pixel tracking. Buttons use normal tracking.
Faces ship as WOFF2.
See Typography.
One card inset
Cards now share a single 20px inset around their content. Section titles, card bodies, list rows, tables, chart headers and the trade ticket all start on the same line, so cards stacked or side by side line up with each other. Previously section cards used 16px and chart cards 20px.
See Card and Section Card.
18 September 2026
Section Card More on the title
Interactive Section Cards (href /
onClick) open from the title row only. Hovering that row slides
More in from the chevron toward the title; More and the chevron
lighten. The rest of the card is not a hit target, so nested tables stay
clickable on their own.
Flush tables match 16px inset
Flush Slick first/last cells use 16px padding, same as vanilla
px-4 and Holdings. Nested Recent activity lines up with Holdings.
Cancelled activity
Cancelled rows strike the name as well as Out/In, and the Type pill is outline grey. The cancelled drawer matches a live order: struck amount, cancelled mark on the logo, Cancelled · Buy in the meta line, no extra Cancelled pill (the stepper already has it).
17 September 2026
Section Card More on the whole card
Interactive Section Cards (href /
onClick) are clickable as a whole. Hover still reveals a label next to
the chevron — now More — and that label plus the chevron lighten.
The title colour does not change.
Cash total in base currency
Cash Card accepts totalLabel. The home
cash section uses Total in {base currency} (for example Total in EUR).
Table footer hairline, not last row
Table / Slick Data Table: the last body row has no bottom border. Pagination sits under a subtle top hairline, like the header split.
Section Card equal inset and flush body
Title-inside Section Cards use 16px
inset on every side (pt-4 / pb-4 / title px-4). Recent activity
passes flushBody so the table sits on the card’s bottom radius like
Holdings.
Activity Out / In and cancelled
Activity columns are Out and In (monochrome: muted vs foreground). Cancelled rows drop the hatch, dim, strike the values, show a red XCircle mark, mute the type pill, and use a red X on the order stepper’s last step. Cancellation reason has no info icon.
Product card hover
Clickable product tiles share productCardClassName({ interactive: true })
— inset hairline plus bg-hover on hover, same as T-bill
cards. Invest category tiles pick this up.
15 September 2026
Tables share header size and stacked pagination
Slick Data Table defaults to
vanilla. Column headers are 15px and stay on one line; on a phone the
pagination footer stacks (results, then page size and pager) instead of
squeezing into one row.
14 September 2026
Field states match the Base System
Field rest, hover, focus, filled, and error follow the input/web catalog: muted label at rest, emphasis on focus and error, a brand focus rim, and the error line below the card.
11 September 2026
Buttons load with the brand mark
Button loading uses the looping unframed mark
on this site, tinted with the button’s text colour and sized with the
control. The notch spinner remains the default where that provider is
not mounted.
Page Loading defaults to a CSS spin
Page Loading and
Session Gate now spin the static wheel —
one direction, no scale, no pulse. The Lottie clip is still
variant="mark"; the older pulse loop is variant="wheel".
App Nav brand mark is slower
Dock collapse / expand / logo hover play the Lottie at 1.5× (was 2×).
App Nav brand mark plays with scroll and hover
The App Nav home mark is the same unframed Lottie as Page Loading. Scroll-collapse plays it forward; returning to the top or hovering the dock plays it back. Hovering the mark itself replays from the start.
Page Loading defaults to the looping mark
Page Loading now defaults to the unframed
Sophic mark — the same Lottie as the marketing header, on its own. It
loops at 1.4×. The earlier CSS pulse-and-spin wheel is still there as
variant="wheel".
Session Gate uses the default, so session restore and other whole-page waits pick this up with no extra props.
9 September 2026
Underline tabs
Tabs gain a fourth list style: underline. A
hairline runs the full width of the list, the labels have more room above
the rule, and the active mark sits on that line — then slides when you
change tabs, same motion as line.
8 September 2026
Section Card titles default to Display
Section Card titles use Helvetica Now
Display Medium 18/24 — the same as Holdings / Activity. titleSize="small"
is Aktiv Grotesk Medium 15/22 and is only used on home Cash, Performance, and
Asset allocation.
Density multipliers are gone
Component size tokens in tokens.css are the Figma literals again — no
--*-density-multiplier wrap. Portal and docs already used 1, so
nothing should shift. Houston still densifies via
admin-ui.css,
not a flat scale. Table density (condensed / default / relaxed) is
unchanged.
Light page fill is Sophic ivory
Light bg-background is #FBF7F4 (--sophic-ivory) with no chart-dots
pattern. The page-background host, canvas transparency, and invest-preview
pattern lab are gone.
Cash, Performance, Holdings, and Activity match the playground
Cash is label/amount rows (no progress bars). Performance is two Change Indicator rows inside one Section Card. Holdings and Activity put the section title + Pill inside the Slick Data Table shell. Home Recent activity uses a Section Card around a flush table so the chevron title sits in the same card.
Section Card title inside the card
Section Card puts the title, count, and chevron inside the card shell — the same chrome as Cash and Asset allocation in the playground — instead of a heading above the card.
Cards use an inset 0.5px hairline
Card (and Slick Data Table shells that share the
same chrome) use Figma card/default: an inset 0.5px border/card
hairline, not an outer ring. Radius stays 16px.
Drawers, menus, and morph shells use 16px corners
Drawer overlay and push rails, Dropdown
Menu (and Select lists that share
--menu-*), and Portal morph docks / trade shells use 16px corners —
the same playground 2xl radius as cards — instead of 20px.
Cards, charts, and inputs use 16px corners
Card, Chart panels, and
standalone Input (and the rest of that family)
use 16px corners — the playground 2xl radius — instead of 24px.
3 September 2026
Holdings near-maturity badge is always Matures in
The Holdings Table warning badge within eight days of maturity always reads Matures in N days (or Matures today). It no longer says Re-invested in N days when auto rollover is on — that copy implied a rollover date the row does not have.
2 September 2026
Skeleton pulse is darker on light
Skeleton pulse bars use --skeleton
(bg-skeleton, neutral-200 on light) instead of --muted, so they
read on white, cards, and mint wash. Known labels stay as text; unknown
values pulse. Holding Money-weighted / Time-weighted tiles pulse an
All-time subtitle row to match the loaded card.
Missing prototypes listed in Demos
Registered prototypes that had a /portal-ui/preview URL but no Demos sidebar page
now have archived pages: Holding auto rollover
and Invest page. Folder-based prototype Code tabs
resolve again. The legacy /portal-ui/preview/buy-sell-morph-dock-prototype URL is
noted on Buy compose dock.
Instrument chart ranges restore 3Y and 5Y
Chart instrument dials put YTD on the primary row and restore 3Y / 5Y in the extra row. Weekly buckets now exist for those windows, so the long-range chips can ship again.
26 August 2026
AMC buy compose
The AMC buy demo locks cash-only market copy for actively managed certificates: Amount to invest, Review (fees, amount invested, latest price, estimated units), and investment details. Before the cutoff the details row is Order deadline; after it becomes Next dealing day (Tomorrow chip). Disclaimers price at the certificate's next available price vs the next business day.
25 August 2026
Chart shell styles in CSS modules
Chart range track, panel frame, skeleton silhouettes, value-display digit roll, and scrub chips keep the same look. Long shell styles now live in colocated CSS modules.
Candlestick scrub sits on the wick
Hovering Chart candlesticks now puts the vertical crosshair through the body, with volume under the same bucket. Fat ranges (few bars) used to draw candles in the gap between ticks, so the line looked completely off.
Holdings Table — name first, near-maturity countdown
Holdings Table now leads with the instrument
name, ticker underneath. Bills and bonds within eight days of maturity
show a warning badge — Matures in N days. Column headers are Name
and % of holdings (compact % below md).
Page Loading — whole-page spinner extracted from Session Gate
New Page Loading component in
@sophic/portal-ui: the faint Sophic wheel that pulses, spins while
shrinking, then grows back. It is the generic whole-page wait for seams that
have no skeleton of their own.
Session Gate is now a thin wrapper that
composes Page Loading with its "Restoring session" label — same props,
pixel-identical render, so RequireAuth fallbacks are untouched.
Portal uses it on the auth seams that previously had no indicator: after a
successful password sign-in (and add-password / "Maybe later"), a full-page
gate shows until /home commits; the magic-link redeem screen's plain-text
"Signing in…" becomes the wheel.
24 August 2026
Country Flag — Cyprus (CY)
Country Flag now ships an updated CY
(Cyprus) mark — the official coat of arms on white instead of the generic
circle-flags glyph. Pass countryCode="CY" as before; the asset lives at
packages/ui/assets/flags/cy.svg.
Instrument charts read the series endpoints
Portal instrument price and yield charts now fetch
GET /instruments/{id}/series/ohlc and /series/yield instead of the
retired GET /price-history / GET /historical-prices. Range dials map to
server-side SeriesPeriod presets (7D → 1w, Max → all); the 3Y/5Y dials
are temporarily hidden until the backend grows those periods with weekly
bars (EPD-1062). No visual
change — same chart, fresher plumbing.
App Nav styles in a CSS module
App Nav keeps the same dock, side rails, labels,
and hover-expand. The long Tailwind chrome now lives as readable CSS next
to the component (app-nav.module.css).
Drawer styles in a CSS module
Drawer keeps the same overlay card, bottom
sheet, and push rail. The long Tailwind chrome now lives as readable CSS
next to the component (drawer.module.css).
Removed holding Details drawer prototypes
The portal shipped the full instrument page (/instruments/[id]), so the
old Holding Detail drawer path — chart + fund sections + key stats +
About in a "Details" pane, with a Your-holding drill-in — no longer exists in
product. We removed its docs mirrors rather than keep them in sync with
nothing:
- Blocks:
drawer-01/dialog-01and their Holding Detail page, plus thehome-01fixture and the Home page it anchored. The shared Home modules (header, metric cards, cards) stay — other demos still use them. - Demos: ETF detail sections and Instrument page. Both were superseded by real product surfaces; the archived list drops their rows.
- Portal: dead drawer-only components (About card, fund drill-in panes, drawer-layout fund cards, duplicate composition parts) are gone; survivors moved next to their consumers (e.g. Key characteristics now lives with the instrument page components).
The frosted shell style itself is unchanged and still documented via the drawers that ship — cash, exchange, returns, trade.
21 August 2026
Softer, denser drawer glass
Drawers use a heavier frost than the app dock — more blur, a slightly stronger light fill — and account menus sit a notch denser. Dock glass is unchanged.
20 August 2026
Bank Icon
Bank Icon is a circular bank mark from a BIC. Known banks show bundled artwork; anything else falls back to a generic glyph.
Slick Data Table styles in a CSS module
Slick Data Table keeps the same
nested card, row wash, and flush padding. The long descendant Tailwind
now lives as readable CSS next to the component
(slick-data-table.module.css).
Date Picker matches Input and Select
Date Picker’s calendar trigger is the same control chrome as Input and Select — height, radius, hairline, and Field nesting. The time field sits on the same row without a shorter outline button beside it.
Date Picker page
Date Picker gets its own page — it was in
the library undocumented. It is the calendar popover for choosing a date
(optional time), not a native <input type="date">.
Date Picker styles in a CSS module
Date Picker keeps the same trigger, calendar
grid, and Clear bar. The Tailwind chrome now lives as readable CSS next
to the component (date-picker.module.css, calendar.module.css).
Label styles in a CSS module
Label keeps the same row, type, and disabled opacity. The Tailwind chrome
now lives as readable CSS next to the component (label.module.css).
Avatar styles in a CSS module
Avatar keeps the same 16 / 32 / 58px circles,
fallback fill, and badge cutout. The Tailwind chrome now lives as readable
CSS next to the component (avatar.module.css).
Pill styles in a CSS module
Pill keeps the same circle, capsule stretch, and
count type. The Tailwind chrome now lives as readable CSS next to the
component (pill.module.css).
Table styles in a CSS module
Table keeps the same hairlines, densities, and
footer fill. The Tailwind chrome now lives as readable CSS next to the
component (table.module.css).
Accordion styles in a CSS module
Accordion keeps the same hairlines, trigger
row, and open / closed caret. The Tailwind chrome now lives as readable CSS
next to the component (accordion.module.css).
Drawer overlay styles in a CSS module
Drawer overlay panels keep the same wash, lg
corners, and right-edge rail. The Tailwind chrome now lives as readable CSS
next to the Sheet primitive (sheet.module.css).
Alert Dialog styles in a CSS module
Alert Dialog keeps the same wash, xl corners, and default / sm sizes. The
Tailwind chrome now lives as readable CSS next to the component
(alert-dialog.module.css).
Dialog styles in a CSS module
Dialog keeps the same overlay, xl corners, and
header / body / footer. The Tailwind chrome now lives as readable CSS next
to the component (dialog.module.css).
19 August 2026
Popover and Hover Card playground
Popover and
Hover Card playgrounds expose align
(start / center / end) next to side, so you can slide the surface along
the trigger without a separate example.
Floating surfaces share an elevation token
Menus, popovers, hover cards, and combobox lists use the same soft
elevation. That recipe is now --floating-surface-shadow (and
--floating-surface-drop-shadow for the filter that survives overflow).
Look unchanged — one place to change it.
Popover and Hover Card pages
Popover and Hover Card get their own pages — they were in the library undocumented. Popover is the click-open surface with interactive content; Hover Card is a richer preview on hover (identifier → name and type). A short label stays on Tooltip.
Which floating surface to pick
The same “You might be looking for” list now sits on Tooltip / Hover Card / Popover.
Card styles in a CSS module
Card keeps the same look and --card-* tokens
(radius, hairline ring, title type). The Tailwind chrome now lives as
readable CSS next to the component (card.module.css). The docs page
lists the tokens it reads.
Hover Card styles in a CSS module
Hover Card keeps the same 16rem surface, lg
corners, border, and --popover fill. The Tailwind chrome now lives as
readable CSS next to the component (hover-card.module.css). The docs page
covers when to pick it over Tooltip or Popover.
Popover styles in a CSS module
Popover keeps the same surface (18rem, lg
corners, --popover fill). The Tailwind chrome now lives as readable CSS
next to the component (popover.module.css). The docs page covers when to
pick it over Tooltip or Hover Card.
Tooltip styles in a CSS module
Tooltip keeps the same inverted pill, 2xl
corners, and arrow. The Tailwind chrome now lives as readable CSS next
to the component (tooltip.module.css).
Radio Group styles in a CSS module
Radio Group keeps the same look and
--radio-* tokens (20px circle, brand fill, white dot). The Tailwind
utility strings now live as readable CSS next to the component
(radio-group.module.css). The docs page lists the tokens it reads.
Checkbox styles in a CSS module
Checkbox keeps the same look and
--checkbox-* tokens (20px soft square, brand fill when checked). The
Tailwind utility strings now live as readable CSS next to the component
(checkbox.module.css). The docs page lists the tokens it reads.
Which overlapping control to pick
The same “You might be looking for” list now sits on the other documented pairs: Switch / Checkbox / Radio Group, Button Group / Input Group, Dialog / Drawer, Input / Textarea / Number Input, and Item List / Table.
Which list control to pick
Select, Combobox, and Dropdown Menu each have a “You might be looking for” list for the other two.
Combobox page
Combobox gets its own page — it was in the library undocumented. It is the searchable field for a long list; a closed set of a few options stays on Select, and commands stay on Dropdown Menu. Empty, selected with clear, and disabled are on the page.
Combobox styles in a CSS module
Combobox keeps the same look. The trigger is
still an Input Group; the open list still uses popover colors and the
shared floating-surface shadow. The Tailwind utility strings now live as
readable CSS next to the component (combobox.module.css). The docs page
lists the tokens the input reads.
Dropdown Menu styles in a CSS module
Dropdown Menu keeps the same look and
--menu-* shell (shared with Select). Item, checkbox, radio, and submenu
APIs are unchanged; the Tailwind utility strings now live as readable CSS
next to the component (dropdown-menu.module.css). The docs page lists
the tokens it reads.
Select styles in a CSS module
Select keeps the same look: default trigger,
chip variant, Field nesting, and the --menu-* list. Variant and size
APIs are unchanged; the Tailwind utility strings now live as readable CSS
next to the component (select.module.css). The docs page lists the tokens
it reads.
Textarea styles in a CSS module
Textarea keeps the same look and --input-* /
--field-control-* tokens. Standalone, Field, and Input Group contexts are
unchanged, including the custom resize handle. The Tailwind utility strings
now live as readable CSS next to the component (textarea.module.css). The
docs page lists the tokens it reads.
Input styles in a CSS module
Input keeps the same look and --input-* /
--field-control-* tokens. Standalone, Field, and Input Group contexts are
unchanged; the Tailwind utility strings now live as readable CSS next to
the component (input.module.css). The docs page lists the tokens it reads.
18 August 2026
Switch and Button Group pages
Switch and Button Group get their own pages — they were in the library but undocumented. Switch is the on/off toggle; Button Group joins matching buttons. Icons or buttons on an input stay on Input Group.
Button Group styles in a CSS module
Button Group keeps the same look (joined
children, separator split, vertical stack, text addon). Orientation is
unchanged; the Tailwind utility strings now live as readable CSS next to
the component (button-group.module.css), with CVA still composing the
orientation API. The docs page covers usage.
Tabs styles in a CSS module
Tabs keeps the same look and --tabs-* tokens.
Default / line / pills and the sliding indicator are unchanged; the
Tailwind utility strings now live as readable CSS next to the component
(tabs.module.css), with CVA still composing the TabsList variant API.
The docs page lists the tokens it reads.
Switch styles in a CSS module
Switch keeps the same look (success track when
on, --neutral-300 when off, --white-fixed thumb). Size default / lg
is unchanged; the Tailwind utility strings now live as readable CSS next to
the component (switch.module.css). The docs page covers usage.
Badge styles in a CSS module
Badge keeps the same look and --badge-* tokens.
Variant and asChild APIs are unchanged; hover is still link-only. The
Tailwind utility strings now live as readable CSS next to the component
(badge.module.css), with CVA still composing the public class API. The
docs page lists the tokens it reads.
Field styles in a CSS module
Field keeps the same look and --field-* tokens.
Orientation and experimental-condensed APIs are unchanged; the Tailwind
utility strings now live as readable CSS next to the component
(field.module.css), with CVA still composing the public class API. The
docs page lists the tokens it reads.
Input Group styles in a CSS module
Input Group keeps the same look and
--input-* / --field-control-* / --dropdown-* tokens. Addon align and
chip size APIs are unchanged; the Tailwind utility strings now live as
readable CSS next to the component (input-group.module.css), with CVA
still composing the public class API. The docs page lists the tokens it
reads.
16 August 2026
MMF buy compose
The MMF buy demo locks cash-only market copy for money market funds: Amount to invest, Review (fees, amount invested, yield, latest NAV, estimated units), and investment details. Before the cutoff the details row is Order deadline; after it becomes Next dealing day (Tomorrow chip). Disclaimers price at the fund NAV for your order vs the next dealing day.
Bond buy compose
The Bond buy demo locks cash-only market copy for zero-coupon and fixed-coupon bonds: Cash to invest, Review (fees, amount invested, yield to maturity, estimated amount at maturity), and investment details. Fixed-coupon adds Est. annual income and Coupon, and the disclaimer notes accrued interest. No reinvest toggle.
15 August 2026
T-bill buy compose
The T-bill buy demo locks cash-only market copy: Cash to invest, Review (fees, amount invested, yield to maturity, estimated amount at maturity), investment details, and Re-invest at maturity. CTA is Buy · up to €10,000.
Buy in shares switcher
The Amount Field hero pill names the other
mode in full: Buy in shares / Buy in cash (sell uses Sell in …).
modeSwitchVerb on DualAmountControl drives that copy.
14 August 2026
Amount Field and dual converter
Amount Field is the cash / shares / limit-price
row: a Field card around Number Input, with an optional trailing badge for
limit vs market. DualAmountControl pairs two ends — hero Shares↔Cash switcher
or connected FX layout — so trade compose and docs share one converter.
Button styles in a CSS module
Button keeps the same look and --button-* /
--icon-button-* tokens. Variant and size APIs are unchanged; the Tailwind
utility strings now live as readable CSS next to the component
(button.module.css), with CVA still composing the public class API. The
docs page lists the Button-scoped tokens.
13 August 2026
Morph Button — expands up, collapses back onto the CTA
Morph Button is the in-place confirm: the idle action grows into a short confirm / success card, and Go back reverse-morphs onto the same button. It should not drop into flow or sit down a pixel at the end.
Tooltip slides with the side + softer corners
Tooltip enter/exit motion is a directional slide
with fade (no zoom) — the tip slides out the same way it came in. Corners
move from rounded-md to rounded-2xl.
12 August 2026
SlickDataTable pagination sits in the tint shell
SlickDataTable pagination no longer floats under the card with a hairline border. It lives inside the outer tint frame under the nested body — same strip as the column header, flipped to the bottom — with matching 15px muted type and a borderless page-size control.
Section Card “View More” on hover
Interactive Section Cards (href /
onClick) reveal a View More label to the left of the chevron when
you hover the title or body on fine-pointer devices. Touch stays
chevron-only. Existing header opacity / focus treatments are unchanged.
App Nav replaces Floating Nav
Product primary navigation is App Nav —
one name for the product split nav and the @sophic/portal-ui rail
primitive. Docs demo uses the Portal split default (destinations + account).
States and breakpoint rules stay on
Responsive Behaviour.
Responsive Behaviour foundations
The locked App Nav / drawer matrix (and other breakpoint forks) lives under Foundations → Responsive Behaviour (schematics + normative rules). The Demos Responsive shell page keeps the preview and points here.
Detail drawer keeps content through slide-out
Closing a Drawer detail sheet no longer blanks the panel before the exit motion — the last entity stack stays rendered through the overlay slide (same as cash/returns). The stack session remounts on the next open so leftover panes cannot stick around.
Profit & Loss copy + compact table header
Metric cards, breakdown footers, and holding drill-in rows spell out
Profit & Loss (no “Total”). Holdings Table
keeps the full header from md up; below md it shortens to P&L with a
tooltip. SlickDataTable columns
accept optional headerCompact for that pattern.
App dock — drawer tuck without breaking scroll-unify
Desktop (lg+, below 1920) drawer-compact now morphs the split track
instead of remounting the mobile joint pill: pack the account shell left,
then merge frost — or, if already scroll-joined, keep the unified bar and
only shrink the gap. Closing clears drawer merge flags so resting
scroll-unify / hover expansion stay intact. Below lg the joint dock is
unchanged (hidden while a bottom sheet is open). Locked matrix + captures:
Responsive Behaviour.
Navbar — split / joined vs packed end poses:
Drawer — bottom sheet vs right rail:
11 August 2026
Compact dock centered + bottom drawer inset wash
Below lg, the joint App dock pill stays centered even when Dock Back
is shown — Back pins to the far left of the padded track instead of
shifting the capsule. Bottom Drawer sheets stop
~10% from the top (h-[calc(90svh-0.375rem)]) and always show a subtle
page wash behind the card (desktop right rails stay undimmed unless
overlay is set). See
Responsive Behaviour.
Portal responsive shell — bottom drawers + compact top dock
Below Tailwind lg (1024px), product Drawer
overlay slides bottom → top as a viewport-fit inset card (desktop keeps
the right 450px rail). Portal’s App dock becomes a joint icons-only top
bar with directional scroll hide/show and hides while a drawer is open.
Side-by-side comparison:
Responsive shell. Guide:
Responsive Behaviour.
Chart scrub — touch sticky + Chart Scrub Tooltip date on crosshair
Chart scrub on coarse pointers keeps the scrub
index after finger-up (idle 10s / outside / range clear) and uses
touch-action: none on the plot. Touch drag wires Recharts onTouchMove
so tint + horizontal crosshair track the activeDot. The vertical arm stays
the Tooltip cursor and picks up the scrub +/- tint. On touch, the date-only
Chart Scrub Tooltip chip (ChartScrubDate) sits above the high price
guide, centered on the vertical line (no amount). Desktop keeps the floating
date + amount Chart Scrub Tooltip chip. Both crosshair arms (including the
tinted vertical) are ReferenceLines driven by scrubIndex.
chartHoustonTooltip is renamed to chartScrubTooltip.
ChartArea idle tint with scrub reveal
ChartArea honors color at rest (still
neutral by default). While scrubbing, the prefix reveal vs series open is
unchanged — instruments pass positive / negative for at-rest tint;
total-value charts stay neutral until hover.
Chart scrub tooltip — time under 1d only
Chart scrub chips take
formatLabel from the API bucket: show hours when
Series.resolution / price-bar interval is finer than 1d (5m,
1h, 5min, …); date only for 1d and coarser (1w). Portal total-value
and instrument charts follow this; the primitives still default to
date+time when formatLabel is omitted.
10 August 2026
Outline button hover + SlickDataTable filter rows
Outline buttons use Neutral 300 for hover (--button-outline-hover). Dark
Figma primitives collapse Neutral 50 and 100 to the same hex, so the old
50→100 hover was invisible (including the Filters trigger). SlickDataTable
filter options use menu item padding tokens so the hover highlight has room
to breathe.
Account menu fill stays denser than app dock
Account menu frost uses glass2MenuProps with a dialable menus strength
(independent of app dock; product default 0.6). Menus glass toggles
frost vs solid popover; strength×opacity wash (card-tinted in dark) so
on/off and the dial both read. Dock product strength stays softer
(0.63 / 0.43) with specular rim off by default.
Dark dock + menus — card-tinted frost
In dark theme, app dock and glass menus no longer use pure-black frost
(reads as a void next to cards). Fill is tinted with --card, and dark
nav/menu opacity is denser so the wash matches solid cards. Portal Glass
lab storage bumped to v3 so stale dial defaults don’t stick.
Portal Glass lab — drawer / menus / app dock rim + frost dials
Experiment picker gains a Glass section: toggle specular rim on drawers, menus, and app dock independently, and dial strength / blur / fill per lane (drawer + nav light/dark). Rim off keeps frost (fill + blur) and only drops the specular edge. Product defaults are rim off (frost + hairline only).
Cash Card onClick
Cash Card and CashCardSkeleton accept optional
onClick, forwarded to Section Card for the
interactive title row with chevron — same pattern as Recent activity.
Solid specular rim off (Card hairline)
Product surfaces no longer paint the directional solid specular rim by
default. Cards, SectionCards, ChartPanels, table shells, and Fields use the
docs Card look: bg-card + 0.5px --card-ring hairline (dark
transparent-swap-dark-8). Radii unchanged.
Still specular / glass-rimmed: buy/sell compose (TradeShell), drawer shell, and floating nav / app dock.
SectionCard frame / SolidCardRim / SectionCardFrameProvider and the
portal Section card rim magic-picker experiment are gone.
solidCardRimStyle + useDocumentDark remain for TradeShell.
Compare: Solid rim → hairline.
7 August 2026
Glass lab moves to docs
Portal no longer ships glass dials. The Konami → experiments panel had a
Glass lab that wrote --glass-2-* overrides to <html> on every load;
portal now renders from glass-2.css defaults and the surface flags in
GLASS_2_SURFACE_DEFAULTS alone. The account-menu Glass switch stays —
it's a product preference, not a dial.
Everything the lab could tune now lives in the
Glass playground: per-lane strengths (page,
cards, nav, drawer, menus), per-theme fill and elevation, the card and solid
card rims, the solid field rim, and the drawer mint wash. One knock-on
change: the mint wash was already forced off in portal, so its CSS default
flipped to off — opt in with data-drawer-tint="on" on <html>.
Glass 2.0 product dials retune
Portal Glass defaults nudge frost stronger on frame: page 0.16, nav
0.75 / 0.34 (light/dark) with brighter rim, drawer + menus 0.85, and
solid card rim highlight 0.65 / #c7c7c7. See
Glass.
5 August 2026
Fix: Drawer glass frost on the host
Follow-up to the blur fix: moving rail/sheet frost onto ::before left the
fixed drawer fully clear (no wash, no blur). Frost stays on the host again —
push rails still slide with right (no transform). Overlay Sheets drop
leftover slide transforms and keep overflow off the frost host (Chromium
flattens backdrop-filter when both sit on the same node); clip moves to an
inner pane.
Fix: Drawer glass frost blur
Push Drawer rails no longer slide with transform — that created a Chromium
backdrop root and left Glass 2.0 translucent without real page blur. Rails
animate right instead (same rule as App Nav). Overlay / mobile Sheet
drawers drop leftover slide transforms under Glass + Drawer.
Field rim comparison
Field shells can wear three rim treatments side by side: product hairline
(--field-ring), solid specular rim (on by default — matches the
solid card rim), and glass card rim + 0.5px hairline (prototype comparison
only). See the
field rim demo or
/portal-ui/preview/field-rim-prototype.
Home greeting time-of-day icons
Product home header greeting follows the viewer’s local clock (morning / afternoon / evening / night) with a small spinning period icon beside the name — morning sun uses quiet spin-slow; other period icons stay static. SSR shows “Hello” until the client clock hydrates.
All four periods: Greeting periods demo.
Spin slow motion style
Animations catalog adds spin-slow —
animate-spin-slow for small decorative glyphs (not loading spinners).
Live demo on the Animations page; home greeting is the first product use.
4 August 2026
Number Input format
Number Input — optional format prop
applies thousands separators and decimal limits as you type (currency,
quantity, integer presets). Omit format for raw string entry.
Country Flag rename
Country Flag (formerly Currency Flag) —
renamed the @sophic/ui atom and replaced the dual-purpose code prop with
mutually exclusive currencyCode / countryCode (exactly one required). Same
resolver and assets; call sites must update the import path and prop name.
Company Logo
Company Logo — circular company mark by ticker or name. Logos load on demand from FMP (no SVG bundle); searchable docs catalog for curated name ↔ ticker mappings (including multi-ticker companies). Initials fallback when the image is missing.
HeightReveal primitive
HeightReveal — height-only accordion for
list See more expands. Lives next to SpringReveal in @sophic/ui. No
scale pop; overflow stays visible when open so hover washes aren’t clipped.
Section Card rim frame
Section Card gained a specular solid rim
(frame="rim"), SolidCardRim, and a portal Section card rim
experiment. Optional titleAccessory sits beside the title after the
count pill. Superseded 10 August 2026 — product hairline only; frame
APIs and experiment removed
(Solid specular rim off).
3 August 2026
New Session Gate
Session Gate — full-viewport session restore
treatment: a faint centered Sophic wheel that pulses, spins while shrinking,
then grows back spinning the other way. Icon only; no “Restoring session…”
copy. Wire as RequireAuth’s fallback.
Colors: teal and violet primitive ramps
Colors primitives now include full Tailwind
teal and violet ramps (50–950), plus info-800 /
success-800 (activity mark tones). Used by portal activity marks
(maturity → teal-700, merger → violet-600, FX → info-800, cash →
success-800). The primitives page splits color ramps from other
primitives (transparent washes / base constants).
ChartBar: hover dims sibling bars
Chart ChartBar soft-dims the other periods while
you scrub one bar (150ms opacity ease), so the active column and its tooltip
stay obvious.
ChartBar: no fake negative floor on all-positive series
Chart ChartBar used to pad the Y domain below
zero even when every bar was positive — inventing a negative tick with no
data under it. Domain padding now only extends on the side that has values
(all-positive floors at 0; all-negative ceilings at 0).
ChartBar: line-chart mono tooltip and axes
Chart ChartBar scrub chip matches the line chart —
mono 10px pill with bold period label + value. Bars stay green / red per
value; the tooltip is foreground (B&W) when every bar is positive, and
green / red when the series includes negatives.
Section Card generic loading skeletons
Section Card ships SectionCardSkeleton —
real title and Card shell with an inset pulse block for non-list bodies.
Item List ships ItemListSkeleton — same
Section Card frame with one pulse bar per row (default three). Use the
list skeleton when the loaded body is an Item List; otherwise use the
generic block.
ChartBar: stronger zero baseline when signed
Chart ChartBar draws a clearer zero line when any
bar is negative — stronger than the grid, centered on zero, and slightly past
the plot edges so positive and negative periods stay readable against a shared
baseline.
Cash Card loading skeleton
Cash Card ships CashCardSkeleton — same
Section Card frame with pulse bars for Total Cash and three currency rows
(flag, code, amount, bar track) so loading reads as the card, not a flat
block.
Stat Metric Card: full amount colored
Stat Metric Card tone="up" /
"down" now colors the whole signed amount — sign and magnitude share
success-700 / error-700 instead of only the + / -.
Change Indicator: xs size
Change Indicator gains size="xs"
(12px semibold) for dense chart change rows. Instrument dials use it;
total-value stays on lg.
ChartValueDisplay size ladder
Chart ChartValueDisplay gains size
(xs · sm · md · lg · xl). lg is the original 42px pair; xl is
48px for total-value headlines; other charts default to md (0.8×). Symbol,
whole, and decimals still scale together with muted colors unchanged.
Total-value change rows use ChangeIndicator lg (15px); instrument charts
use the new xs (12px).
Chart plot: more vertical inset
Chart sparkline margins add a bit more room above
and below the series (CHART_PLOT_MARGIN top 16 / bottom 16) so the change
row and range control don’t crowd the line.
Instrument chart: quieter defaults + change readout
Instrument dials default to Y axis off and Details panel hidden
(price guide and volume stay on). The change row matches other charts:
amount and parenthesized percent on ChangeIndicator, with a comma
decimal on the percent ($12.34 (2,60%)).
Total value delta: percent matches amount color
Chart total-value headlines put the range percent
in parentheses on the same ChangeIndicator as the amount — success /
destructive instead of muted, with a comma decimal and two places (4,13%).
Candlestick scrub: OHLCV card + denser series
Chart candles scrub with the same tinted mono chip as the line chart — date header plus Close / Open / High / Low (+ Vol). Axis style no longer swaps that for a single-amount pill. Dense scrub also skips duplicate index flushes, keeps the vertical cursor prop stable, memoizes candle shapes, and wraps the plot so parent digit-roll updates don’t redraw every wick.
Scrub crosshair vertical arm a bit stronger
Chart scrub’s vertical cursor sits at a slightly higher opacity and stroke weight than the horizontal arm so it reads clearly in both light and dark — without matching full solid.
Candlestick scrub matches area smoothness
Chart ChartCandlestick now coalesces pointer
moves to one frame (same rAF flush as ChartArea), memoizes candle rows /
domains, and wraps in memo so dense OHLC scrub stays smooth with the
crosshair and digit roll.
Stat Metric Card loading skeleton
Stat Metric Card ships
StatMetricCardSkeleton — same Card frame with pulse bars for title,
amount, and subtitle so loading reads as a tile, not empty space.
Total value chart: start/end guides + scrub chip
Chart total-value compositions use the same
priceGuide style as instrument dials — muted low/high dotted levels with
corner value labels on scrub, plus the tinted date + amount chip and H+V
crosshair. ChartValuation gains priceGuide / tooltip /
referenceLines so Contributions mode stays in sync.
Holdings table: Profit/Loss cell
Holdings Table renames the column to Profit/Loss. Amount and parenthesized percent stack without the change chevron; both lines share the same success/destructive color.
Glass cards: Figma rim lane + solid specular fallback
Frosted cards get their own Glass 2.0 rim lane — independent of page / nav /
shell — with product defaults 150° / 0.35 / fill 1, card strength
0.55, a Figma 0.5px hairline under the edge, an independent bottom-right
catch (330° / 0.25), and light/dark rim colors (#ffffff). Tune in the
lab, the Glass playground (Preview → card), or the
card rim prototype.
When Cards frost is off, Solid card rim is on by default — opaque fill
- primary/BR rim at
150°/0.2/ BR0.25·#c4c4c4/#c9c9c9. Shell surface frost defaults off; drawer / menus stay on. Drawer frost default strength is0.63.
2 August 2026
Holdings table: tighter columns
Holdings Table headers read Qty and Value (was QTY / Total). Unrealized P&L and P&L % share one stacked P&L cell (amount over percent). The Day column is gone — day change stays on holding detail.
1 August 2026
Activity detail: cancel dock for pending orders
Pending activity drawers reserve a sticky cancel dock again. When
is_cancellable is true, the morph confirm (Cancel → Yes/Keep → Done)
shows; when false, muted This order can't be cancelled. Side-by-side in
Activity detail. The old pending-order
drawer demo and home pending-orders card were retired — cancel lives on
activity only.
Chart details strip uses SpringReveal
Toggling Details panel in instrument dials now opens and closes with the
same spring-pop / soft-dismiss
SpringReveal as the gear settings — no hard mount/unmount. Mounting already
open skips the enter pop so a default-on strip does not bounce on first paint.
31 July 2026
Chart range: hold, pulse, then morph
Range switches keep the previous series on screen (keepPreviousData) with a
soft opacity pulse and a translucent skeleton blend over the plot
(ChartPanel refreshing) — the series stays mounted. When the new points
arrive, ChartArea morphs toward them (~350ms).
Intermediate frames share one vertex count with the end state (downsample when
dense) so the path never hard-cuts. Cold loads still use the full skeleton.
ChartArea path morph on range change
ChartArea eases the plotted path when data swaps
(about 350ms) so changing 1D → 6M no longer snaps. Intermediate frames
resample to a fixed point count, then settle on the exact target series.
Respects prefers-reduced-motion (snap). Pass animateData={false} to opt
out. Portal / demos pick this up automatically.
Chart axes + price guide
ChartArea / ChartCandlestick gain optional xAxis,
yAxis, and priceGuide (plus a tinted date + amount scrub chip). Instrument
gear adds Axes (X/Y checkboxes) and Price guide; defaults are Y + guide
on. Price guide draws muted levels on the plotted series low/high (close for
area, wicks for candles); corner labels appear on hover (inset further on faded
edges). Scrubbing keeps a darker H+V crosshair. Scrub indicator is a small solid
core with a soft color glow. Plot margins stay edge-to-edge — Y ticks reserve
width only, and the free side still edge-fades.
Drawer sections: one SectionCard language
Holding “Transactions” was a hand-rolled title + Card; Key characteristics used
ad-hoc rows. Both now match Activity / pending-order facts: SectionCard +
ItemList. Docs holding chart drops the frosted override so it uses the same
solid ChartPanel as portal.
Fix: Card hairlines inside glass drawers
Nested Card / SectionCard bodies (and table bodies) inside a glassed
drawer were losing their hairline — Glass 2.0 set box-shadow: none to
strip frost elev, which also killed Tailwind ring. Activity detail
“Activity” / “Breakdown” (and ETF holding SectionCards) looked uncarded
next to the frosted identity tile. Drawer nests stay solid and restore the
0.5px card ring (inset on ChartPanel).
Removed: Activity page layout experiment + chart range loupe
Retired the activity-page-layout magic-picker experiment and the legacy
ItemList Activity page demo — portal /activity is table-only. Removed the
experimental chart-range loupe (magic-picker lab + ChartRangeControl
loupe prop); range selection keeps the solid sliding pill.
Chart scrub tint + digit roll
ChartArea stays neutral at rest. While scrubbing, the
prefix to the cursor tints green or red vs series open (new "negative" color),
with a dotted vertical cursor and no floating scrub pill. Recharts string
activeTooltipIndex is parsed correctly.
Dense series stay smooth: the tinted overlay is a static path revealed by a
clip edge (and a green/red flip vs origin) instead of remapping every point on
move; scrub updates coalesce to animation frames. onScrub also passes the
series index so parents skip findIndex.
ChartValueDisplay rolls digits on value change
(Balanced motion by default): enter scales up with a 1.1 magnify settle, exit
scales down, color fades muted ↔ full. Pass scrubIndex while scrubbing so
roll speed tracks pointer motion (indices/sec) — fast scrub collapses stagger
and cancelled delays snap so digits never freeze. Portal total-value and
instrument charts use both.
30 July 2026
Glass elev + style lab dials
Light elevation is a whisper (0.03 / 20px; dark 0.12). Fancy top navbar
keeps soft elev (like AppNav); only the picture-frame shell zeros elev.
Fancy frost hosts drop overflow-hidden so Chromium doesn’t flatten
backdrop-filter. Shell frost dials live under Top shell; fancy dials
under Fancy top navbar (disabled when that shell / Shell surface / Glass
is off).
ChartPanel footer inset
ChartPanel layout="responsive" restores bottom padding on the footer
slot (e.g. Contributions) and the mobile range control. A recent stacked-range
fix had zeroed panel pb whenever a footer range existed, which left
desktop footer style flush against the card corner while the header range
still sat in the padded header.
Tabs sliding indicators
Tabs (also called Segmented Control in docs) selection indicators now glide between options — same motion language as the chart range control. Default and pills use a sliding fill (soft frosted bubble while moving, then solid); line uses a sliding underline (or side bar when vertical). Both orientations. Respects reduced motion.
Activity page
Portal /activity lists account activity via
GET /accounts/{id}/activity (Open = status === reserved) in a
holdings-style SlickDataTable (Date · Activity · Type · Sent ·
Received, pending row tint). Nav Activity and Recent activity link here.
Open rows enrich via GET /orders/{id}; cancellable orders cancel from the
activity drawer.
Demo: Activity table.
Activity detail drawer
Portal Activity detail deepens to a Figma Transaction layout: hero,
facts, breakdown, and an order card when referenceType is order (stepper,
fills, fees, settlement date). Docs prototype covers ETF / FX / Bond / Cash /
Other fixtures; FX adds Currency Conversion from cash legs; bonds add Clean /
Accrued / Total. Order stepper orientation is an experiment
(activity-order-stepper). Fees aggregate labeled Fees (not Commission) —
breakdown revisit EPD-814.
Demo: Activity detail.
Instrument chart dials
Instrument / fund price charts can opt into richer dials on the same OHLCV series: expandable ranges, a gear settings panel (line ↔ candles icons, volume, details), volume subplot, dotted reference lines, and an OHLC details strip.
New Chart primitive ChartCandlestick — scrub
opens a Close / Open / High / Low card (plus Vol when volume is on). Settings
open/close use spring-pop /
soft-dismiss via SpringReveal (overshoot 1.02).
Canonical frame: Charts blocks · Instrument dials.
Animations foundation
New Animations catalog for named motion
styles (shared vocabulary for humans and agents). First pair: spring-pop
enter and soft-dismiss exit — CSS utilities plus the SpringReveal
helper that pairs them. Docs include classname samples and a components
section.
29 July 2026
App Nav without search companion
App Nav no longer shows the search circle beside the dock. Demos and the Home block match.
Dark card and chart hairlines
In dark mode, Figma’s border/card is 0% opacity — so solid
Card and Chart panels lost
their edge against the page wash (light still had a neutral-300 ring).
Dark now approximates the glass hairline with transparent-swap-dark-8.
Slick Data Table shares Card’s
radius and ring tokens; Field keeps its 16px
radius but tracks --card-ring.
Cash Card row layout
Cash Card currency rows match Asset allocation: label (flag + code) and amount on one line, progress bar full-width below.
Dark cards stay solid (no wash bleed)
In dark mode, Figma’s transparent-dark-50 card fill is flattened to an
opaque shade over the page background (same look, no see-through). Fields
follow, and Slick Data Table keeps the
nested body card in both themes — rows on bg-card, header on the outer
tint. Frosted Glass surfaces still use the Glass 2.0 fill recipe.
Chart grid (light)
Light-theme chart plot dots use the same foreground whisper as dark
(foreground at 14%), with opacity 0.6 so the grid stays visible on
white cards. Dark stays at 14% / 0.48.
28 July 2026
Drawer push modes
Drawer can reshape the page instead of only
overlaying it. Wrap the page in DrawerLayout and set mode to
push-reserve (width inset), push-scale (recess), or push-translate
(shift left). The rail is flush full-height; below sm push falls back to
fullscreen. Overlay (mode="overlay") stays the inset Sheet. Compare in the
drawer mode playground; product Holding Detail switches via the Drawer Mode
experiment (default overlay).
DrawerStack covered-pane wash
When a pane is pushed, Drawer
softens the covered layer with a light white wash in light theme (dark
keeps a light black veil). shift uses the same shell dim as the other
animations, and zoom-slide is a bit snappier.
Drawer docs + DrawerStack
Drawer documents the edge panel (Sheet under the
hood) and DrawerStack for push/pop panes inside the same open drawer.
Animation variants (slide, zoom-slide, shift, settle) are exposed
in the DrawerStack playground; product default is shift-fade.
Holding Detail uses it for Details → Your
holding.
Home layout — chart + metrics column
Home top row stacks returns, cash, and asset allocation beside a taller total-value chart; holdings spans the full width below. Pending orders shows five rows to match recent activity.
Tabs track contrast
Tabs default list track uses a clearer fill so the segmented control reads on the light page wash — same track language as chart range controls.
24 July 2026
App Nav placement and hover expand
App Nav accepts placement
(bottom | top | left | right) with a spring-ish entrance from that
edge, plus hoverExpandLabels so an icons dock can reveal labels on desktop
hover. On left/right when expanded, the pill grows sideways only (right →
left, left → right); each item is a full-width list row at the unexpanded
icon-hit height, icons on the dock edge, equalized to the longest label.
expandDurationMs / collapseDurationMs tune enter vs leave. Labeled
layout still collapses on narrow phones.
Stat Metric Card subtitle ReactNode
Stat Metric Card subtitle accepts a
ReactNode so change rows (ChangeIndicator, etc.) can sit under the
amount. Plain strings still render as muted body text.
New: Stat Metric Card
Stat Metric Card — labeled metric with an
optional subtitle. Pass an unsigned amount; tone adds the colored
+ / -.
Card family variants on docs pages
Card and Section Card now list linked variant tiles (Section Card, Item List, Cash Card, Stat Metric Card) so the composites are findable from the base Card page.
New: Item List
Item List is a Section Card body list —
same row padding whether static or selectable, with edge-to-edge
bg-hover hover when onSelect is set. Use it for pending
orders / activity-style rows; keep Slick Data Table for columnar data.
Glass 1.0 removed — Glass 2.0 only
Legacy html.surface-glass / glass.css token remaps are gone.
Foundations → Glass and the docs shell toggle
use Glass 2.0 (html.portal-glass) with the product surface defaults
(menus + drawer on; cards / charts / tables off).
Section Card as Component
Section Card moves from Blocks into
Components (@sophic/portal-ui) — title row with optional count Pill and
exclusive href / onClick chevron over a Card shell. Cash Card
composes it. The old Blocks section-card page is removed; Home links to the
component page.
Cash Card + Progress Bar as Components
Cash Card and
Progress Bar move from Blocks into
Components (@sophic/portal-ui). Currency Flag
documents the shared @sophic/ui flag atom. The old Blocks cash-card page
is removed; Home links to the component pages.
23 July 2026
Glass 2.0 portal defaults + surface groups
Foundations → Glass documents what portal ships:
strength lanes (page 0.15 / nav 0.65 / drawer 0.49 / menus 0.78),
fill light/dark 0.66 / 0.66, surface toggles (drawer + menus on; cards /
charts / tables off), and nested content inside a glass drawer staying solid.
The lab dials start from those defaults. Glass 1.0 is noted as legacy
docs-only.
Portal Glass: Glass 2.0 page-wide (nav recipe)
Product Glass switch enables html.portal-glass — the App Nav Glass
2.0 wash + rim — on cards, ChartPanel, SlickDataTable, sheet, and menus.
Legacy Glass 1.0 milky remaps stay off on portal. Nav / account menu use
the same recipe via glass2Props.
Glass 2.0 product defaults + App Nav migration
Glass 2.0 ships the lab export as :root defaults (base light 1.25,
blur 10px, fill light 0.11, rim hi 0.66, surface strength 0.6,
edge rim). App Nav surface="glass" uses glass2Props.
New: Glass foundation (Glass 2.0 lab)
Foundations → Glass documents legacy Glass 1.0
(html.surface-glass) vs Glass 2.0 — the App Nav frost recipe
opted in with data-glass="2" / glass2Props({ strength, edge }). Global
--glass-2-base-light / --glass-2-base-dark multiply per-surface
strength. The lab dials theme (iframe), edge, blur, fill, rim knobs, and
preview shape (card / panel / pill / dropdown).
Light glass menus use a frosted fill
Under html.surface-glass, light-mode dropdowns / selects now set
--menu-bg to a translucent wash (same idea as dark). They were falling
back to solid --popover and reading as opaque white cards.
App Nav size ramp
App Nav accepts
size="sm" | "default" | "md" | "lg" | "xl" | "2xl". Default keeps the
Figma dock; lg is the usual product-home scale. Every step scales items,
icons, trailing, and the search companion. The companion inherits the dock
size when omitted. Try the full ramp in the playground.
22 July 2026
New: App Nav
App Nav is the bottom dock for primary
product navigation — items, optional profile trailing slot, and a search
companion. It lives in @sophic/portal-ui with surface="glass" (default)
or surface="solid", and layout="icons" or layout="labeled" (active item
is a filled pill with icon + label).
Glass follows the iOS playground Tab bar (Neutral 300 selected pill + Figma Glass radius 14). CSS can’t do Figma’s refraction, so the shell uses a light wash + 6px blur and a directional rim edge (brighter top-left, dimmer bottom-right — not a uniform ring) instead of a milky 65% fill. That’s the default in light and dark on the demo, overview, and product. Frost is not gated on the docs glass toggle. The component demo and playground stage stripes (and other busy backgrounds) so light glass doesn’t read as a flat opaque pill; temp dials cover blur / rim.
Dark glass uses the same rim treatment with a denser wash so stage color reads through without a hard outline. Floating menus (Select / dropdown) also got denser dark frost so style behind them doesn’t show through sharp when glass is on.
Field card frame matches product playground
Field card fill, ring, and padding follow the iOS playground product Field — not the surface-glass card remaps — so light stays solid white with a Neutral 300 hairline and dark uses the dark wash plus a glass-edge ring. Disabled is a solid Neutral 200 card with muted value text (not an opacity fade).
Field experimental condensed
Field gains
variant="experimental-condensed" — tighter padding around the card
and denser control metrics inside it. Named experimental so we can
promote it to condensed or drop it.
Field helper below the card
Field help text and errors sit below the card now — outside the input container — instead of inside it. Nested helpers on choice rows stay with the control.
Input text centering
Input placeholder and value sit on the vertical center of the field again.
Checkbox size, mark, and hover
Checkbox matches the radio footprint (20px) with softer corners and the bold check mark. Checked uses the brand green and deepens on hover; unchecked hover darkens the border.
Radio unchecked hover
Radio unchecked hover darkens the border the same way as checkbox, so both controls feel interactive before selection.
Number Input Max chip
Number Input Max is the same small grey pill as the currency picker, so the two controls match.
Accordion trigger hover
Accordion trigger hover is quieter: text
shifts to Neutral 700 (muted-foreground) instead of underlining.
Ghost button hover
Button ghost now uses a clear Neutral 200
hover fill (--button-ghost-hover), matching the intentional hover
treatment on the other variants.
Badge hover and weight
Badge hover is link-only across variants
(Neutral 200 for ghost/outline via --badge-ghost-hover), the link
variant is removed, type weight steps down to Medium, and the demo
shows a static row plus an asChild link row.
Card hairline ring
Card edge is a 0.5px Neutral 200 ring
(--card-ring-width / --card-ring) instead of a heavier 1px border,
matching the Figma hairline more closely.
Change Indicator chevrons
Change Indicator uses the Base System sort-up mark — a softer rounded triangle that inherits the status color, flipped for negative — with a bit more space before the value.
Chart value tracking and range pill
Chart headline figures drop the wide display tracking, and the active range segment is a flat white pill with a 0.5px Neutral 200 edge instead of a drop shadow.
Dialog header subtitle clearance
Dialog keeps the subtitle in the text column — it no longer runs under the close control, with a clear 12px gap beside the X.
Tabs selected pill
Tabs selected state is a white pill with a fuller radius and a light 0.5px edge — no drop shadow — matching other segmented controls.
Input family softness
Input, Textarea, Select, and Input Group share one standalone frame: larger radius, a light 0.5px edge, roomier padding, and body type — aligned with Field cards. Select chevrons and Input Group addon icons use a bolder weight.
Input
Textarea
Soft style plus a custom bottom-right resize handle (native grip hidden). Hover scales the handle and shows a grab cursor.
Select
Input Group
Field
Number Input actions
Max and the currency chip use the same corner softness as the amount field — fully rounded to match the input shell.
Number Input invalid color
On error, the amount stays the normal text color — only the message under the field turns red.
Input Group note footer
Input Group block footers (like a note with Send) use smaller footnote-sized label text and more vertical room so the icon button’s hover isn’t clipped by the rounded shell.
Components overview grid
Components now opens on an Overview page — a responsive four-column grid of square tiles (collapses on smaller widths), each with a compact base preview and a link to that component’s docs.
21 July 2026
New: Table and SlickDataTable
Two table surfaces ship under one docs page — Table:
- Table (
@sophic/ui) — the shadcn composition (Table,TableHeader,TableBody,TableRow,TableHead,TableCell, …) for custom markup. - SlickDataTable (
@sophic/portal-ui) — framed holdings-style table with sticky header, synced horizontal scroll inside the body card, click-to-toggle sort, and adensityprop (condensed/default/relaxed; defaultrelaxed). Width follows column sizes instead of a fixed min-width.
Component previews and playgrounds scale to fit the docs column.
20 July 2026
Brand fonts: Aktiv Grotesk + Aktiv Grotesk
Brand faces ship from @sophic/fonts — Aktiv Grotesk for
--font-display, Aktiv Grotesk for --font-helvetica-text,
Aktiv Grotesk for --font-sans, and SF Mono for --font-mono. See
Typography.
Density multipliers on component tokens
Every component group with a size ramp exposes a density multiplier
(default 1) so apps can scale height / type / padding without forking
the Figma ladder or changing component APIs:
--button-density-multiplier--badge-density-multiplier--field-density-multiplier--menu-density-multiplier--dropdown-density-multiplier--change-density-multiplier--avatar-density-multiplier--modal-density-multiplier--radio-density-multiplier--pill-density-multiplier--card-density-multiplier--chart-density-multiplier
Size metrics use calc(<figma-rem> * var(--*-density-multiplier)).
Radius and other shape tokens are not multiplied, so densifying an
app does not reshape pills or menu corners. Number-input (colors only)
and shimmer (no size ramp) have no multiplier.
Apps opt in by setting the multipliers (Houston uses 0.78 for all of
them in its token bridge). Docs and other Base System consumers leave
them at 1 and look unchanged. See
Foundations.
17 July 2026
New: Chart
Portfolio charts ship in @sophic/portal-ui/components/ui/charts — a ChartPanel
shell, ChartArea and ChartValuation primitives, range control, value
display, and chart tokens for light and dark. The plot area includes a dot
grid behind the series (on by default). Area fill uses a vertical gradient —
darker near the line, fading to transparent toward the baseline — clipped to
the region under the curve only.
Chart documents usage; three reference frames live under Charts blocks. Block previews default to Inline so charts fill the docs column — switch to Desktop / Tablet / Mobile when you need breakpoint testing.
Button size ramp: lg / md / sm
Text buttons now follow Figma's three-tier size ramp: lg at 44px,
default (md) at 36px, and sm at 28px — with matching padding, gap,
and typography per tier (md uses Text md/Medium; lg and sm use Text
lg/Medium). The old default size was 44px; it drops to md so lg
picks up the former default height.
Button colors in light and dark
Button fills now match the Figma example rows in both themes — primary flips to white-on-black in dark, outline and danger-tint pick up the mode-flipping error and neutral ramps. Capture shows the designed light and dark rows side by side.
New: Brand Icon
The sign-in pinwheel in its rounded square is now
Brand Icon: BrandIcon for app icons and
favicons, SophicWheel when you only need the mark on your own surface.
Pre-sized favicons ship under @sophic/portal-ui/assets/favicons/. Product apps
use the forest-green frame; this docs site uses a hot-pink frame
variant so the tab is easy to spot — shown once on
Foundations. size is one of 16, 24, 32, 48, 56,
180, 192, or 512 px (default 32).
Props tables show defaults at a glance
Component Props tables now have a dedicated Default column with a
highlighted pill — no need to expand a row to see 32, "brand", and
the like.
Brand Icon page: Wheel Mark demo
The Brand Icon component page includes a
live SophicWheel preview (standalone pinwheel on neutral and brand
surfaces), alongside the framed BrandIcon examples.
Foundations token tables, easier to scan
Component and primitive color tokens on Foundations are grouped by component (Button, Field, Dropdown, …), collapsed in compact rows by default, and linked from the page sidebar — pick a group and it expands in place.
Field Composition documented
Foundations → Field Composition explains how card fields, labels, and unstyled controls fit together — for designers and PMs, not as a token dump.
15 July 2026
New: Change Indicator
The delta readout from the Base System: a filled caret and a value on the status ramps — Text md/Medium at the default size, Text sm/Semibold small. Formatting stays in app code; the component renders what you pass it.
New: Avatar
The Base System avatar lands: circles at 16, 32, and 58px with a text fallback while images load, and a badge slot on the bottom-right edge — cut out by page-background padding — for the design's buy and sell transaction markers.
Dialogs get the designed header
Dialog headers take the Base System modal header: a Text xl/Medium title, the 32px outline-circle close button, and a hairline divider when bordered — all through --modal-* tokens, with houston's current dialogs preserved by the fallbacks.
Menus float like the design
Open menus — select lists and dropdown menus alike — take the Base System menu: a 4px-padded surface at a 20px radius with the soft drop shadow and card ring, and 38px pill rows in Text md/Medium. Hover and the selected row fill neutral-300, with the check trailing. Everything flows through --menu-* tokens shared by Select and Dropdown Menu; houston keeps its current menus through the fallbacks.
The dropdown chip lands on Select
The Base System dropdown — the compact pill that picks a currency on the
number input — is now variant="chip" on the Select trigger: 28px
secondary pill, hover deepen, disabled fades the label. Chips keep their
style inside a Field, so the
Number Input currency row now works as
designed.
Sign-in block follows the updated mock
The sign-in design got a fresh pass in Figma and the block now matches
it. A weak password reads as red instead of orange, the wrong-password
error is the design's shorter "The password you entered is incorrect.",
the check-your-email badge shows an open envelope, and the page-footer
terms line quietens down to regular-weight links. Card field labels now
sit on their designed 18px line with the mock's tighter tracking,
carried by new --field-label-* tokens with fallbacks that leave
non-token apps untouched.
Radios go brand green
Radio buttons take the Base System look: 20px circles with a 1.5px ring, and the selected state fills the brand green (deepening on hover) with a constant-white dot — one of the few places the palette uses brand color for state rather than neutral. Sizes and colors flow through --radio-* tokens; houston keeps its current 16px radios through the fallbacks.
New: Number Input
The designed money-entry row lands as Number Input: a large display-type value with a muted currency prefix, a clear affordance, and room for trailing actions like a Max button. It composes inside a Field, which contributes the card and the focus and error rings — nothing is duplicated. The designed currency selector row follows with the dropdown component.
Shimmer learns the brand colors
Shimmer gains variant="brand": the sweep
runs a khaki core with green fringes from the mode-flipping brand ramps
instead of the monochrome highlight. Apps without the token file fall
back to the default sweep automatically.
Icon Buttons follow the Base System
Icon Buttons are circular under the Base System (they already inherited
the pill radius) and now get their designed size ramp: icon is 44px and
icon-sm grows from 28px to the designed 32px. The secondary and outline
fills matched the icon button page exactly, so variants are untouched.
14 July 2026
Card fields get their designed focus ring
Focused card fields now show the sign-in design's card/active effect —
a thin neutral-900 inner ring with a soft neutral-300 halo. This
matters for accessibility too: the card disables the inner control's own
focus ring, so until now keyboard focus had no visible indicator. Works
for inputs, textareas, and select triggers alike.
Choice cards select cleanly
Wrapping a field in its own FieldLabel makes the whole card clickable.
The label and the card used to draw two outlines on top of each other —
now the label carries a single card, and the selected option shows a
primary border with a subtle tint.
Standalone inputs get a surface
Bare inputs, textareas, selects, and input groups were transparent, which
read wrong against the light theme's tinted background. They now paint a
white surface from the new --input-bg token — a derived value until the
Base System gets an input catalog page. Inside a Field
nothing changes: the card still carries the frame.
New pages and a smarter Field playground
Accordion, Input Group, and Textarea get their own pages — they were in the library but undocumented. The Field playground now switches between all five control types instead of only showing the input composition.
Component playgrounds preview at real viewports
The Playground section on component pages gets the same viewport switcher as the preview above it. Mobile, tablet, and desktop modes render the component in a real frame at that width, and the controls keep working live. The Field page also gains validation-error and field-group examples plus a per-subcomponent props reference.
Fields wear the Base System card
The card-style field from the sign-in design is now what
Field renders: the wrapper is the card, the
label sits inside it, and Input /
Input Group go unstyled within — same markup as every form today,
new skin, with errors on the standard data-invalid. No input catalog
page exists in the Base System yet, so finer states are provisional.
First real page: the sign-in flow
The placeholder login block is replaced by the designed sign-in flow — an interactive walk from email through password (with change, forgot and magic-link paths), check-your-email, and add-a-password with a strength meter, in the three designed layouts: centered, tinted, and split panel with artwork. The signed-in end screen is a demo-only stand-in until the design defines one.
Blocks get a variations playground
Block pages with flow states or variations now carry a Playground section: pick a layout, drop into any step, force error states, and switch viewports — every combination is a shareable URL.
Buttons follow the Base System
Buttons are now pills — 44px tall (28px for sm) with 16px medium text.
Hover shades, and the disabled state, come straight from Figma: disabled
buttons turn solid grey instead of fading. There's also a new solid
danger variant for destructive primary actions; the existing tinted
destructive stays for lower-emphasis ones. In dark mode the default
button flips to white while danger text stays white on red.
Badges become status tints
Badges shrink to 22px pills with semibold text and a hairline border, and
pick up the status ramps: new info (blue) and warning (orange)
variants, with destructive mapping to the red tint.
New: Pill counter
A small circular counter for unread counts and totals — stretches into a
capsule for wider values like 99+. See Pill.
Card borders and titles
Cards use the Base System's card border (invisible in dark mode, where the surface goes translucent instead) and titles move to the display family at Heading/Medium size.
Docs: props tables realigned
Props tables now lay out on a proper three-column grid, so the header lines up with the content, long union types wrap cleanly, and nothing runs under the expand chevron.
10–14 July 2026
The Base System lands
The Figma Base System became @sophic/ui/tokens.css: mode-flipping color
ramps, the Figma semantic aliases, a bridge onto the shadcn token names,
the type scale (system stack for text, Archivo standing in for Helvetica
Now Display), and explicit radii. The Foundations
section documents all of it, rendered from the shipped file so it can't
drift. The docs site is the first adopter; houston/console keep their
current look until their own migration.