Sophic Interface Systems

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.

Before
After

Before
After

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.

After

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.

Before
After

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.

Before
After

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.

Before
After

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.

After

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.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

Before
After

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.

Before
After

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.

Before
After

Before
After

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.

Before
After

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.

Before
After

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:

TokenLightDark
--outer-borderSoil neutral-800 @ 9%white @ 5%
--outer-border-strongSoil @ 17%white @ 9%
--inner-borderSoil @ 6%white @ 3%
--hairline-width1px; 0.5px at ≥2dppxsame

--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

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.

Before
After

Warning alert

Alert has a warning variant: warning wash behind the message, warning text on top.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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 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.

After

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.

After

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.

After

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.

Before
After

Cash total in base currency

Cash Card accepts totalLabel. The home cash section uses Total in {base currency} (for example Total in EUR).

After

Table / Slick Data Table: the last body row has no bottom border. Pagination sits under a subtle top hairline, like the header split.

Before
After

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.

After

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.

After

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.

Before
After

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".

Before
After

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.

Before
After

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.

Before
After

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.

After

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.

After

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.

Before
After

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

Before
After

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.

Before
After

Before
After

Before
After

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.

Before
After

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.

Before
After

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.

After

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.

Before
After

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).

Before
After

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.

Before
After

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-01 and their Holding Detail page, plus the home-01 fixture 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.

After

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.

Before
After

After

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">.

After

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.

After

After

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.

After

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 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.

After

After

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.

After

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.

After

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.

After

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.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

Before
After

After

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:

Before
After

Before
After

Drawer — bottom sheet vs right rail:

Before
After

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.

Before
After

Before
After

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).

After

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.

Before
After

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.

Before
After

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.

After

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.

Before
After

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 — 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.

After

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.

Before
After

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).

Before
After

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.

After

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.

Before
After

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.

After

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 + / -.

Before
After

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.

After

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).

After

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.

After

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 / BR 0.25 · #c4c4c4 / #c9c9c9. Shell surface frost defaults off; drawer / menus stay on. Drawer frost default strength is 0.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 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.

Before
After

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.

Before
After

Before
After

Before
After

Before
After

Before
After

Before
After

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.

After

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.

Before
After

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.

After

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.

After

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).

Before
After

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.

Before
After

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.

Before
After

Input text centering

Input placeholder and value sit on the vertical center of the field again.

Before
After

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.

Before
After

Radio unchecked hover

Radio unchecked hover darkens the border the same way as checkbox, so both controls feel interactive before selection.

Before
After

Number Input Max chip

Number Input Max is the same small grey pill as the currency picker, so the two controls match.

Before
After

Accordion trigger hover

Accordion trigger hover is quieter: text shifts to Neutral 700 (muted-foreground) instead of underlining.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

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.

Before
After

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

Before
After

Textarea

Soft style plus a custom bottom-right resize handle (native grip hidden). Hover scales the handle and shows a grab cursor.

Before
After

Select

Before
After

Input Group

Before
After

Field

Before
After

Number Input actions

Max and the currency chip use the same corner softness as the amount field — fully rounded to match the input shell.

Before
After

Number Input invalid color

On error, the amount stays the normal text color — only the message under the field turns red.

Before
After

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.

Before
After

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 a density prop (condensed / default / relaxed; default relaxed). 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.

After

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.

Before
After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

After

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.

Before
After

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.

Before
After

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.

After

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.

After

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.

Before
After

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.

Before
After

New: Pill counter

A small circular counter for unread counts and totals — stretches into a capsule for wider values like 99+. See Pill.

After

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.

Before
After

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.

On this page

6 October 2026Section card headers fit narrow cardsCrisper modal edge, firmer field borderPassword reset asks for the email first5 October 2026Four heading levelsDisplay is now FigureFrost is more opaqueColour tokens are rolesWhat's inside weight rows stack on phonesSecondary buttons are solid4 October 2026Avatar rim is optionalCurrency pairs the flag with the codeFrost is menus and nav onlyEdges are 1px, not a hairlineDark primary buttons step up to 900Table rows hover and stay selectedWordmark follows the page inkQuestionnaire choices use the choice tokensMenus keep frost; the glass switches are gone3 October 2026Dark fields sit above the cardControl edges sit above layout edgesControl metrics follow the spacing scaleChoice controls sit on the label lineDark choice cards use fill, not a white ring2 October 2026Progress Bar — tiny fills stay visibleDark controls sit above the card1 October 2026Text input — fill vs edgeError / red ramp from FigmaSection Card / Divider — no surfaceLayout border set (outer / inner / strong)Section Card APIOne layout borderSofter light card edgesValuation scrub chip shows contributions30 September 2026Modal layersKey value emphasisThe washed palette is the defaultButton type matches FigmaSet B colors follow the rampsAlert titleWarning alertQuestionnaireAlertSet B warning uses amberToastFolder style tabsConfirm Dialog29 September 2026Key Value list APIKey Value borderlessSplit CardDividerSection Card title sizes md smHeading and DisplayBank icons load from the logo CDNKey Value loading rowsDialog padding and widthsDialog backdrop and shadowDialogs sit on whiteDialog footer borderFlags and the brand icon use avatar sizesAvatar sizes follow a 4px rampKey Value row densityLarge buttons are pillsAlternative dark controls sit above the cardTall input matches the large buttonDate Picker can block days28 September 2026New Key ValueSection Card heading can take a byline26 September 2026Alternative neutrals shift a stepAlternative progress bars use a brand trackAlternative palette is a step lighterLimit price no longer compares to market25 September 2026Info Callout24 September 2026Dialog body lines up with the titleSection shadowTable row hoverSegmented control trackDisplay small is 24pxPrice guide labelsShared color rolesBadge borders stay on outline onlySmall text is 14pxAvatar xs and xl23 September 2026Cash and performance titles on small screensTighter tabular figuresBrand face: Aktiv GroteskOne card inset18 September 2026Section Card More on the titleFlush tables match 16px insetCancelled activity17 September 2026Section Card More on the whole cardCash total in base currencyTable footer hairline, not last rowSection Card equal inset and flush bodyActivity Out / In and cancelledProduct card hover15 September 2026Tables share header size and stacked pagination14 September 2026Field states match the Base System11 September 2026Buttons load with the brand markPage Loading defaults to a CSS spinApp Nav brand mark is slowerApp Nav brand mark plays with scroll and hoverPage Loading defaults to the looping mark9 September 2026Underline tabs8 September 2026Section Card titles default to DisplayDensity multipliers are goneLight page fill is Sophic ivoryCash, Performance, Holdings, and Activity match the playgroundSection Card title inside the cardCards use an inset 0.5px hairlineDrawers, menus, and morph shells use 16px cornersCards, charts, and inputs use 16px corners3 September 2026Holdings near-maturity badge is always Matures in2 September 2026Skeleton pulse is darker on lightMissing prototypes listed in DemosInstrument chart ranges restore 3Y and 5Y26 August 2026AMC buy compose25 August 2026Chart shell styles in CSS modulesCandlestick scrub sits on the wickHoldings Table — name first, near-maturity countdownPage Loading — whole-page spinner extracted from Session Gate24 August 2026Country Flag — Cyprus (CY)Instrument charts read the series endpointsApp Nav styles in a CSS moduleDrawer styles in a CSS moduleRemoved holding Details drawer prototypes21 August 2026Softer, denser drawer glass20 August 2026Bank IconSlick Data Table styles in a CSS moduleDate Picker matches Input and SelectDate Picker pageDate Picker styles in a CSS moduleLabel styles in a CSS moduleAvatar styles in a CSS modulePill styles in a CSS moduleTable styles in a CSS moduleAccordion styles in a CSS moduleDrawer overlay styles in a CSS moduleAlert Dialog styles in a CSS moduleDialog styles in a CSS module19 August 2026Popover and Hover Card playgroundFloating surfaces share an elevation tokenPopover and Hover Card pagesWhich floating surface to pickCard styles in a CSS moduleHover Card styles in a CSS modulePopover styles in a CSS moduleTooltip styles in a CSS moduleRadio Group styles in a CSS moduleCheckbox styles in a CSS moduleWhich overlapping control to pickWhich list control to pickCombobox pageCombobox styles in a CSS moduleDropdown Menu styles in a CSS moduleSelect styles in a CSS moduleTextarea styles in a CSS moduleInput styles in a CSS module18 August 2026Switch and Button Group pagesButton Group styles in a CSS moduleTabs styles in a CSS moduleSwitch styles in a CSS moduleBadge styles in a CSS moduleField styles in a CSS moduleInput Group styles in a CSS module16 August 2026MMF buy composeBond buy compose15 August 2026T-bill buy composeBuy in shares switcher14 August 2026Amount Field and dual converterButton styles in a CSS module13 August 2026Morph Button — expands up, collapses back onto the CTATooltip slides with the side + softer corners12 August 2026SlickDataTable pagination sits in the tint shellSection Card “View More” on hoverApp Nav replaces Floating NavResponsive Behaviour foundationsDetail drawer keeps content through slide-outProfit & Loss copy + compact table headerApp dock — drawer tuck without breaking scroll-unify11 August 2026Compact dock centered + bottom drawer inset washPortal responsive shell — bottom drawers + compact top dockChart scrub — touch sticky + Chart Scrub Tooltip date on crosshairChartArea idle tint with scrub revealChart scrub tooltip — time under 1d only10 August 2026Outline button hover + SlickDataTable filter rowsAccount menu fill stays denser than app dockDark dock + menus — card-tinted frostPortal Glass lab — drawer / menus / app dock rim + frost dialsCash Card onClickSolid specular rim off (Card hairline)7 August 2026Glass lab moves to docsGlass 2.0 product dials retune5 August 2026Fix: Drawer glass frost on the hostFix: Drawer glass frost blurField rim comparisonHome greeting time-of-day iconsSpin slow motion style4 August 2026Number Input formatCountry Flag renameCompany LogoHeightReveal primitiveSection Card rim frame3 August 2026New Session GateColors: teal and violet primitive rampsChartBar: hover dims sibling barsChartBar: no fake negative floor on all-positive seriesChartBar: line-chart mono tooltip and axesSection Card generic loading skeletonsChartBar: stronger zero baseline when signedCash Card loading skeletonStat Metric Card: full amount coloredChange Indicator: xs sizeChartValueDisplay size ladderChart plot: more vertical insetInstrument chart: quieter defaults + change readoutTotal value delta: percent matches amount colorCandlestick scrub: OHLCV card + denser seriesScrub crosshair vertical arm a bit strongerCandlestick scrub matches area smoothnessStat Metric Card loading skeletonTotal value chart: start/end guides + scrub chipHoldings table: Profit/Loss cellGlass cards: Figma rim lane + solid specular fallback2 August 2026Holdings table: tighter columns1 August 2026Activity detail: cancel dock for pending ordersChart details strip uses SpringReveal31 July 2026Chart range: hold, pulse, then morphChartArea path morph on range changeChart axes + price guideDrawer sections: one SectionCard languageFix: Card hairlines inside glass drawersRemoved: Activity page layout experiment + chart range loupeChart scrub tint + digit roll30 July 2026Glass elev + style lab dialsChartPanel footer insetTabs sliding indicatorsActivity pageActivity detail drawerInstrument chart dialsAnimations foundation29 July 2026App Nav without search companionDark card and chart hairlinesCash Card row layoutDark cards stay solid (no wash bleed)Chart grid (light)28 July 2026Drawer push modesDrawerStack covered-pane washDrawer docs + DrawerStackHome layout — chart + metrics columnTabs track contrast24 July 2026App Nav placement and hover expandStat Metric Card subtitle ReactNodeNew: Stat Metric CardCard family variants on docs pagesNew: Item ListGlass 1.0 removed — Glass 2.0 onlySection Card as ComponentCash Card + Progress Bar as Components23 July 2026Glass 2.0 portal defaults + surface groupsPortal Glass: Glass 2.0 page-wide (nav recipe)Glass 2.0 product defaults + App Nav migrationNew: Glass foundation (Glass 2.0 lab)Light glass menus use a frosted fillApp Nav size ramp22 July 2026New: App NavField card frame matches product playgroundField experimental condensedField helper below the cardInput text centeringCheckbox size, mark, and hoverRadio unchecked hoverNumber Input Max chipAccordion trigger hoverGhost button hoverBadge hover and weightCard hairline ringChange Indicator chevronsChart value tracking and range pillDialog header subtitle clearanceTabs selected pillInput family softnessNumber Input invalid colorInput Group note footerComponents overview grid21 July 2026New: Table and SlickDataTable20 July 2026Brand fonts: Aktiv Grotesk + Aktiv GroteskDensity multipliers on component tokens17 July 2026New: ChartButton size ramp: lg / md / smButton colors in light and darkNew: Brand IconProps tables show defaults at a glanceBrand Icon page: Wheel Mark demoFoundations token tables, easier to scanField Composition documented15 July 2026New: Change IndicatorNew: AvatarDialogs get the designed headerMenus float like the designThe dropdown chip lands on SelectSign-in block follows the updated mockRadios go brand greenNew: Number InputShimmer learns the brand colorsIcon Buttons follow the Base System14 July 2026Card fields get their designed focus ringChoice cards select cleanlyStandalone inputs get a surfaceNew pages and a smarter Field playgroundComponent playgrounds preview at real viewportsFields wear the Base System cardFirst real page: the sign-in flowBlocks get a variations playgroundButtons follow the Base SystemBadges become status tintsNew: Pill counterCard borders and titlesDocs: props tables realigned10–14 July 2026The Base System lands