Cardona CT LabAI Tooling · Design & Accessibility
Pre-hire design audit · Brand · UX Friction · WCAG / 508

SoLo Funds design audit

A three-track review of the SoLo Funds brand and product surface: brand-system consistency, UX friction mapped through Friction Inversion, and WCAG 2.2 / Section 508 accessibility. Built before the conversation, because the clearest way to show how I work is to do the work.

Subject: solofunds.com + app Tracks: Brand, UX, Accessibility Method: Friction Inversion Author: Jason Cardona
1
Critical
8
High
7
Medium / Low
16
Findings

An audit should practice what it preaches. This report passes WCAG AA in Day, Night, and High-Contrast modes. Try the toggle, top right.

SoLo ships a confident, distinctive brand: Druk Wide display type, a warm orange and cream palette, the rotating coin motif. The gap is not taste, it is governance. The production theme has no design tokens, so the same intent is re-typed as raw values across hundreds of rules, and the brand drifts as a result.

The solofunds.com homepage hero: a large group photo under the SoLo wordmark and the headline Community finance is the answer, with light navigation across the top.
The solofunds.com homepage, captured live for this audit. Confident photography and a strong wordmark, with the navigation contrast and image-quality issues flagged below.

Palette, as extracted from the live theme

Brand orange
#F5873A · primary
Yellow
#FCBE24 · accent
Soft purple
#9E98F4 · surface
Olive green
#617831 · surface
Green gradient
#24BB67 to #009499
Teal
#007478 · accent
Cream
#F3F1EC · surface
Ink
#000000 · text
Filter
B-01

No design tokens: the system is hardcoded values

HighDesign systemMaintainability
8.6

Finding

The theme defines zero CSS custom properties across 5,694 lines. Every color, type size, and radius is written as a literal value, so the brand lives as 314 hardcoded hex colors scattered through 990 page-template-scoped selectors. There is no single source of truth, so a brand change means a find-and-replace across the stylesheet, and intent silently forks (see B-02).

Evidence, measured from style.css

solofunds.com theme stylesheetstyle.css → 5,694 lines, 130 KB :root { --* } custom properties → 0 hardcoded hex color literals → 314 .page-template-* selectors → 990 (per-page overrides, not shared tokens) #F5873A (brand orange) repeated → 20 times

Why it matters

This is the foundation a design system is built on, and it maps directly to the Month 3 milestone. Tokenizing the existing palette and type scale into CSS variables is a low-risk, high-leverage first move: it makes the brand governable, makes accessibility fixes global instead of per-rule, and collapses the hundreds of page-scoped overrides into a handful of semantic aliases.

B-02

Brand colors are not single-sourced

HighConsistency
7.6

Finding

Because there is no token (B-01), the same brand color exists as more than one value. The primary orange appears as both #F5873A and #F5863B. The green family is four uncoordinated values used interchangeably for buttons, gradients, and surfaces.

Evidence

Distinct hexes for one brand intentorange → #F5873A and #F5863B (two "primary" oranges) green → #617831 (olive) · #24BB67 · #009499 · #007478 (one role, four values)
#F5873A
#F5863B

Recommendation

Collapse to one canonical --color-brand and a named green scale. The drift is invisible in isolation and obvious side by side, which is exactly the kind of error a token layer prevents by construction.

B-03

Two display typefaces compete across templates

MediumTypography
5.7

Finding

Headlines use Druk Wide on most pages but switch to Poppins on the Solo IQ, Bankroles, and landing templates. Body text is GoghVariableGX in most places and Poppins in others. The voice changes depending on which template a visitor lands on.

Evidence

Heading font-family by templatedefault / home / community → 'Druk Wide' solo-iq / bankroles / lp → 'Poppins' body → 'GoghVariableGX' (Poppins on #poppins templates)

Recommendation

Define a type system with one display face and one text face, mapped to semantic roles. If Poppins is intentional for a sub-brand, scope it as a named theme rather than a per-template override.

B-04

Defects shipped in the production stylesheet

MediumCode quality
5.2

Finding

Several rules in the live theme are broken or placeholder, which means parts of the intended design never render. These are caught by a linter in seconds and point to a missing CSS quality gate.

Evidence

From the live solofunds theme CSS.filter .active → background-image: url(.../ICON_NAME_HERE.svg) (placeholder shipped) footer a → text-decoration: ffffff; (invalid value, rule ignored) .header-images → // border-radius: 24px; (// is not a CSS comment; line is invalid)

Recommendation

Add stylelint to CI. Each of these is a one-line fix, but their presence in production signals there is no automated check between authoring and deploy.

B-05

The brand display face ships without a woff2

LowPerformance
3.2

Finding

The deployed site already self-hosts its fonts well: every face carries font-display: swap, and the body face GoghVariableGX and all of Poppins are served as woff2. One gap remains. Druk Wide, the brand display face, is delivered as an .otf with no woff2, so it downloads larger than it needs to and is not subset to the characters a headline uses.

Evidence, from the live font stylesheet

@font-face on solofunds.com (optimized build)GoghVariableGX → woff2 + ttf font-display: swap Poppins → woff2 font-display: swap Druk Wide → .otf only font-display: swap (no woff2)

Recommendation

Add a woff2 for Druk Wide and subset it to the headline character set. This is the last mile of a font setup that is otherwise already optimized, so it is a small, isolated win.

B-06

A modern image pipeline, undercut by one GIF and over-compressed photos

MediumPerformanceImage quality
5.1

Finding

Credit where it is due: the homepage already serves images as WebP through an optimizing CDN, and the total image payload is light. Two things pull it back down. The single heaviest asset is a half-megabyte animated GIF, and the hero photography carries visible compression banding, the kind that reads as cheap on an otherwise premium brand.

Evidence, measured from the live page

solofunds.com homepage, captured with Playwright and Resource Timingimage requests → 13 (1.43 MB total over the wire) content images via WebP CDN → 6 (.bv.webp) heaviest single asset → 0.50 MB animated GIF (SoLo IQ chat) hero and section photos → visible banding and blocking
A section of solofunds.com showing a model against a smooth background, with visible banding and blocking artifacts from over-aggressive image compression.
Section photography on solofunds.com. The banding in the smooth background is a sign the WebP quality is set too aggressively for photographic content.

Recommendation

Replace the GIF with a muted autoplay video or animated WebP, which typically cuts its weight by around eighty percent. Raise the CDN quality for hero photography, where banding shows most, and keep aggressive compression for flatter graphics. The WebP pipeline itself is the right foundation, worth keeping.

SoLo is a peer-to-peer lending app whose users often have the least margin for error. That raises the stakes on UX friction: a confusing field or an unanchored decision is not a minor annoyance, it is a barrier to capital. This track maps each friction point through Friction Inversion: the principle that the correct action should be the path of least resistance.

How to read this track
CurrentWhat the present design makes easy, including the wrong or anxious choice.
InvertedThe smallest change that makes the right choice the easy one.
On evidence. SoLo gates the product behind a connected bank account: you cannot browse the marketplace or evaluate the experience without first linking a financial account, which is the first finding below. The observations here are drawn from SoLo's own product renders, publicly posted user captures (including r/Sololender), and that onboarding gate observed directly. Each finding rests on a real artifact, not a guess. Screen capture from a funded account is the one path that would deepen this track further.
SoLo borrow flow render: stacked cards reading How much do you want to request ($100), How much do you want to pay ($10), and When can you pay back your loan, under the headline Your Terms. No judgment. No traps.
SoLo's own rendering of the borrow flow. The amount to repay above principal is entered as an answer to "How much do you want to pay," then surfaces to lenders as a "tip." See U-2.

Friction findings

Filter
U-1

The product is gated behind a connected bank account

HighOnboardingTrust
8.2

Friction score

8.2

Current state

A new user cannot browse the marketplace, see real requests, or judge whether the model fits them without first linking a bank account. The highest-trust action the app will ever ask for is the price of admission to look around.

Why it matters

SoLo's users are often the most cautious about granting financial access and the most exposed if it goes wrong. Demanding the deepest commitment before returning any value inverts the trust curve: maximum risk, zero context. It also blocks evaluation, which is the reason this audit leans on external captures.

Friction Inversion opportunity
CurrentLinking a bank account, the riskiest step, is required just to look.
InvertedLet people explore the marketplace and understand the model first. Ask for the bank link at the moment of the first real lend or borrow, when its purpose is obvious and the value is already clear.
U-2

The cost of borrowing is framed as an optional "tip"

HighBorrowerClarity
8.0

Friction score

8.0

Current state

The borrow flow asks "How much do you want to pay?" and the marketplace lists that amount to lenders as a "Lender Tip," for example $30 on a $550 request. Functionally this is the price of the loan and the lender's yield, but the label "tip" frames it as optional rather than as the cost it is. The borrower is left to infer what amount gets the request funded, while lenders filter and sort by tip percentage.

Evidence

Product renders and public marketplace capturesborrow flow → "How much do you want to pay?" ($10 on $100) marketplace → "Lender Tip: $30.00 (5%)" · filter: "Tip Percentage" borrower note → "Bills; Max Tip $30;"
Friction Inversion opportunity
CurrentThe word "tip" reads as optional, so the borrower is left inferring what amount gets a request funded.
InvertedShow total repayment and an effective rate next to the tip field, so the borrower can read the real cost directly instead of inferring it. Make the real number the easy number to see.
U-3

A public reason field sits beside score and tip

MediumBorrowerDignity
6.4

Friction score

6.4

Current state

Each request shows a public free-text "Reason" beside the borrower's Solo Score and tip, for example "emergency vet bill," and lenders browse these as a feed. If the reason and tip influence how quickly a request is funded, the design quietly favors disclosing more and tipping more.

Evidence

Marketplace request card (public capture)Solo Score 99 · Requested $550 · Lender Tip $30 (5%) · Reason: emergency vet bill
Friction Inversion opportunity
CurrentIf reason and tip drive funding speed, the way to compete is to disclose more and tip more.
InvertedLet the Solo Score and repayment history carry creditworthiness, make the reason optional and structured rather than free text, and avoid ranking that could favor the most distressing reason.
U-4

Account errors dead-end into an email address

HighError recoveryTrust
7.5

Friction score

7.5

Current state

A publicly posted capture shows the marketplace covered by a modal: "Oops! There is an error with your account that has caused it to become blocked. Please contact help@solofunds.com." The only control is "Got It." No reason, no in-app path, no timeline, on the screen the user opened to move money.

Evidence

Blocking modal (public capture, r/Sololender)title → "Oops!" body → "...your account ... has become blocked. Please contact help@solofunds.com..." actions → [ Got It ] (dismiss only; no cause, no recovery)
Friction Inversion opportunity
CurrentAt the highest-stress moment the app offers a generic apology and an email address.
InvertedName the cause where it is known, give an in-app resolution path or status, and set an expectation for time to resolve. A block is sometimes unavoidable, a dead-end is not.
U-5

The wallet blends spendable cash with SoLo Credits

MediumWalletClarity
5.6

Friction score

5.6

Current state

The wallet headline sums Cash and SoLo Credits into one balance, both in the same amber, and the transaction list carries a second unlabeled figure beside each amount. A user asking "how much can I actually withdraw" has to infer the answer.

Observed on a wallet screen with sample data, so this is a structure and hierarchy note, not a live-balance claim.

Friction Inversion opportunity
CurrentOne blended balance hides what is spendable right now.
InvertedLead with the withdrawable amount, separate credits visually and say what they do, and label the running balance in the transaction list.

Color contrast measured from the live theme, foreground over background exactly as the CSS pairs them. Every ratio below is computed, not asserted. The headline: the primary call to action fails WCAG AA, and the white-on-color pattern fails across orange, purple, and yellow. The fix pattern is encouraging, because black text on the same orange and yellow already passes.

Measured contrast, as used

Pair, as usedRatioAA
White on orange #F5873A · primary CTA2.50Fail
Orange #F5873A text on white · links2.50Fail
White on purple #9E98F42.55Fail
White on yellow #FCBE241.68Fail
Gradient green #24BB67 heading on white2.51Fail
Gray #B3B3B3 caption text on white2.10Fail
Black on orange #F5873A8.41Pass
Black on yellow #FCBE2412.53Pass
White on teal #0074785.57Pass
White on olive #6178314.95Pass
Filter
W-01

Primary call to action fails contrast

CriticalWCAG 1.4.3 AA508 §1194.22(c)
9.1

Finding

White text on the brand orange #F5873A measures 2.50 to 1, well under the 4.5 to 1 required for normal text. This is the primary button hover state and the floating download button, so it sits on the most important conversion path in the product.

Evidence

Computed contrast, white on #F5873Aforeground #FFFFFF / background #F5873A → 2.50 : 1 (AA needs 4.5 : 1) sources: .btn-primary:hover, #download-button

Recommendation

Two clean options, both already proven in their palette. Use black text on the orange, which measures 8.41 to 1 and passes comfortably, or darken the orange to roughly #B45309 for white text. Black on orange keeps the energy of the brand while clearing AA.

W-02

Orange as link and interactive text fails on white

HighWCAG 1.4.3 AA
7.7

Finding

Orange #F5873A set as text on a white surface measures 2.50 to 1. It is used for text links and the .btn-text call-to-action pattern, so interactive affordances are hard to read for low-vision users.

Evidence

Computed contrast, orange text on white#F5873A on #FFFFFF → 2.50 : 1 (fail) sources: .btn-text, in-content links, .page-template-service #slider .btn-text

Recommendation

Reserve orange for large display use and fills with black text. For inline links and small interactive text, use a darker brand value such as the existing teal #007478 at 5.57 to 1, or an orange darkened to meet 4.5 to 1.

W-03

White-on-color surfaces fail across purple and yellow

HighWCAG 1.4.3 AA
7.2

Finding

The white-text-on-color pattern is applied to surfaces that are too light to carry it. White on soft purple #9E98F4 is 2.55 to 1, and white on yellow #FCBE24 is 1.68 to 1, which is effectively unreadable.

Evidence

Computed contrast#FFFFFF on #9E98F4 (.bg-purple) → 2.55 : 1 (fail) #FFFFFF on #FCBE24 (nav hover) → 1.68 : 1 (fail) #000000 on #FCBE24 → 12.53 : 1 (pass, already on the .solofunds circle)

Recommendation

Switch text on light fills to black, which their own coin component already does on yellow. Codify a rule in the token layer: light surfaces take ink text, dark surfaces take white. This is the kind of decision a token system enforces automatically (see B-01).

W-04

Gradient headings and gray body text fail on white

MediumWCAG 1.4.3 AA
5.9

Finding

The green gradient eyebrow and heading style (.h9) renders text at #24BB67 over white, measuring 2.51 to 1. Secondary gray text at #B3B3B3, used for captions such as the Solo IQ slide text, measures 2.10 to 1. Both fail AA for the small sizes they are used at.

Evidence

Computed contrast#24BB67 on #FFFFFF (.h9 gradient text) → 2.51 : 1 (fail) #B3B3B3 on #FFFFFF (Solo IQ slide captions) → 2.10 : 1 (fail)

Recommendation

Darken gradient text toward the teal end and below, and lift secondary gray to at least #767676 for 4.5 to 1 on white. Reserve the bright green for large or decorative use only.

W-05

Header navigation is light text over photography

HighWCAG 1.4.3 AA508 §1194.22(c)
7.4

Finding

Over the hero, the header renders the primary navigation (Lend, Borrow, Bank, SoLo IQ, Log In) as light text directly on the photograph, with no scrim behind it. Where the photo is light, which is most of the hero, the links fall below the 4.5 to 1 minimum, and because the backdrop is an image the exact ratio shifts from pixel to pixel.

Evidence

Captured hero and theme CSSheader over hero → background: transparent #menu-primary-navigation a → color: #ffffff result → white nav on a light group photo, no scrim
The solofunds.com top navigation rendered as light text over a light photographic background, where the links are difficult to read.
The navigation reads as light text on a light photo. Contrast is not guaranteed, because the background is an image.

Recommendation

Guarantee contrast independent of the photo: add a subtle gradient scrim behind the header, or give the nav a solid background from the start rather than only after scroll. This is the one contrast issue here that varies with content, which is why it needs a structural fix rather than a single color change.

The whole audit has one throughline: a missing token layer. No tokens (B-01) lets the brand drift (B-02) and makes every contrast failure a separate accident (W-01 through W-05). So the fix is one artifact, not a dozen. Below is the first version of that layer, with the Track 3 failures already resolved, and the primary fix shown in context. This is where the Month 3 design system starts, and it is roughly a day of work.

The primary fix, in context

Current · white on orange
Request a loan
2.50 : 1 · fails AA
Proposed · ink on orange
Request a loan
8.41 : 1 · passes AA, brand intact

Contrast resolutions

RoleCurrentProposed
CTA and color fillswhite on orange 2.50ink on orange 8.41
Link text#F5873A 2.50#BF540A 4.68
Muted text#B3B3B3 2.10#767676 4.54
Success / eyebrow text#24BB67 2.51#1A884B 4.50
Purple surfacewhite on purple 2.55ink on purple 8.25
Yellow surfacewhite on yellow 1.68ink on yellow 12.53

The token foundation

solo-tokens.css · first version, contrast-safe by construction:root{ /* surfaces */ --color-bg:#FFFFFF; --color-surface:#F3F1EC; --color-ink:#1A1A1A; /* brand fills — always paired with ink text */ --color-brand:#F5873A; /* CTA orange · ink on it = 8.41:1 */ --color-accent-yellow:#FCBE24; /* ink = 12.53:1 */ --color-accent-purple:#9E98F4; /* ink = 8.25:1 */ --on-fill:#000000; /* fills that carry white text */ --color-teal:#007478; /* white = 5.57:1 */ --color-olive:#617831; /* white = 4.95:1 */ --on-dark:#FFFFFF; /* text colors — all >= 4.5:1 on white */ --text-muted:#767676; /* was #B3B3B3 (2.10 -> 4.54) */ --link:#BF540A; /* was #F5873A (2.50 -> 4.68) */ --success-text:#1A884B; /* was #24BB67 (2.51 -> 4.50) */ }

Adopting this is a day, not a quarter: define the variables once, then swap the repeated hex literals for them. Every Track 3 failure is resolved by construction, the brand drift in B-02 cannot recur because there is one source of truth, and the orange, yellow, and coin energy of the brand is untouched. The fixes move text and pairings, not the palette. This file is the seed of the design system the role asks for in Month 3.

The throughline across all three tracks is a single missing layer. SoLo has a strong brand and a real product, but no design system underneath them, so consistency, accessibility, and velocity all pay the same tax. Tokenizing the existing brand is the highest-leverage first move, and it is exactly where the role's roadmap points.

Alignment with the role roadmap

Stated milestoneThis artifact
Month 1 · Audit brand and product suiteThis report, across Tracks 1, 2, and 3.
Month 1 · Identify optimization areasSixteen findings, prioritized by severity.
Month 2 · Define a visual North StarDirectional, with a contrast-safe token starter delivered in the Proposed tab.
Month 3 · Formalize a design systemA first token foundation is sketched in the Proposed tab, ready to build on.

Compliance posture

A fintech accessibility obligation

SoLo handles money for the users who can least afford to be locked out, so contrast failures on the primary lending path are an inclusion issue first. ADA Title III makes accessible design an obligation here, not only a preference.

The fix pattern is favorable

The critical contrast failures resolve with black-on-orange and black-on-yellow, treatments already present in the brand. This is a governance fix, not a rebrand.

About this audit

Track 1 reflects direct inspection of the live SoLo Funds theme stylesheet, plus a live capture of the homepage with Playwright that recorded every image request and its weight. Track 3 contrast ratios are computed from the extracted colors with the standard WCAG relative-luminance formula. Track 2 draws on SoLo's own product renders, publicly posted user captures, and an onboarding gate observed directly, since the app blocks browsing behind a bank-account link. Each finding is scored 0 to 10 as a weighted composite of user impact, scope, and inverse fix complexity.

Let us talk about what comes next