Skip to content
BlackFig

Teardown: redesigning a vibe-coded finance dashboard

AntPublished 7 min read

Quick answer

The typical AI-built finance dashboard has the right parts and no priorities: a gradient header, four identical stat cards, a rainbow chart and placeholder rows. Redesigning it means picking the question users open it to answer, then rebuilding around that with real data, one accent colour, a readable chart and a table that shows what needs action.

On this page
  1. The app
  2. What works
  3. What is broken
  4. The redesign
  5. Lessons

The app

This is the first in a series of teardowns where I take a typical AI-built screen and redesign it, showing every decision. To be clear up front: this is an illustrative concept I designed for the series, not a client's app. The before is a composite of what AI builders commonly produce when you ask for a finance dashboard. The after is how I would redesign it.

The imagined brief: a small UK business wants to see where its money is going. The founder prompts an AI builder with something like "build a finance dashboard for a small business, modern and clean". A few minutes later, they have this:

  • A full-width header with a purple-to-blue gradient and the title "FinanceHub Dashboard", with a money-bag emoji in front of it.
  • Four identical stat cards in a row: Total Balance, Income, Expenses and Savings. Each has an icon inside a tinted circle and a figure with a dollar sign.
  • A bar chart of monthly spending, with each bar a different colour of the rainbow.
  • A "Recent Transactions" list with rows named Transaction 1, Transaction 2 and Transaction 3, each with an amount and a date in month-day order.
  • Everything centred: the title, the cards' contents, the chart heading and the list.

It took minutes to generate, it looks finished at first glance, and it would get a nod in a demo. That is what AI builders are for, and it did that job well.

What works

It is worth being fair about this, because the first draft gets a lot right.

  • The structure is reasonable. Summary at the top, trend in the middle, detail at the bottom is a sensible order for a dashboard.
  • The components are solid. The cards, buttons and chart come from shadcn/ui and Recharts, which are well built and accessible underneath. Nothing needs replacing at component level.
  • It is responsive. The card grid collapses to one column on a phone.
  • It suggests a data model. Accounts, transactions, categories and months are all implied. That is a useful starting point for the real schema.
  • It is fast to change. Because it is all generated React and Tailwind, every change below is an edit, not a rebuild.

What is broken

Nothing here throws an error. The problems are all decisions that were never made.

There is no main question

Four cards of equal size say that balance, income, expenses and savings matter equally, on every day, to every user. They do not. A small business owner opening a finance tool usually wants to know one of two things: how long the cash will last, and what is about to go out. Neither is on the screen.

Decoration in place of hierarchy

The gradient header takes the most valuable space on the page and says nothing. The emoji, the tinted icon circles and the centred text all add visual activity without adding meaning. Centring every block also removes the left edge the eye uses to scan down a working screen.

The wrong currency and date format

Dollar signs and month-day dates on a UK product are a small detail with a large effect: they tell the user the product was not made for them. It also hints at a deeper problem, since currency is being hard-coded as a string rather than formatted from data.

A chart that is hard to read

Nielsen Norman Group's guidance on dashboards is that people do not perceive hues as ordered, so colour should not encode quantity. Here it does not even encode category: each month gets its own colour for no reason. The eye tries to find meaning in the colours and there is none. The chart also only shows spending, so it cannot answer "am I bringing in more than I spend".

Placeholder data

"Transaction 1, 2, 3" hides whether the table works with real content: long payee names, refunds, pending items, foreign currency card payments. Placeholder data makes every layout look tidy.

No actions and no states

There is nowhere to act: no bill to pay, no receipt to attach, no account to connect. And there is no designed empty, loading or error state, so a new user with no bank connection would see four £0 cards and an empty chart, which looks broken. Empty states, errors and loading states AI forgets covers why that matters.

The redesign

I reframed the product as what the brief actually describes: spend management for a small UK business. The dashboard answers two questions, in this order. How long will the cash last? What needs my attention this week?

Layout

  • A sidebar with accounts. Each connected account (business current account, savings, company card) with its balance, plus the total. Balance stops being a stat card and becomes navigation: click an account to filter everything else.
  • A headline row with cash runway as the largest figure, and money in and money out for the period beside it, smaller.
  • A money in vs money out chart below, across the main column.
  • Upcoming bills on the right: the next payments due, with date, payee, amount and the account it leaves from.
  • A transactions table at the bottom with receipt status on every row.

Cash runway, not "Total Balance"

Runway is the number a founder actually worries about. I calculate it from the last three full months, so one unusual month does not swing it wildly, and I handle the case where the business is cash positive instead of showing a meaningless huge number:

// lib/runway.ts
type Month = { inPence: number; outPence: number }
 
export function cashRunwayMonths(balancePence: number, lastThreeMonths: Month[]) {
  const months = lastThreeMonths.length || 1
  const avgNetBurn =
    lastThreeMonths.reduce((sum, m) => sum + (m.outPence - m.inPence), 0) / months
 
  if (avgNetBurn <= 0) return { kind: "positive" as const }
  return { kind: "months" as const, value: balancePence / avgNetBurn }
}

The card then reads "7.4 months of runway at your average spend over the last 3 months", or "Cash positive over the last 3 months" when money in exceeds money out. The explanation sits under the number in smaller text, so the user knows what it is based on.

A chart that answers a question

The chart compares money in and money out per month. Money in uses the one accent colour; money out uses a neutral grey. The tooltip shows both values in pounds, so the user does not have to read them off the axis:

"use client"
 
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"
import { formatMoney } from "@/lib/format"
 
const chartConfig = {
  inPence: { label: "Money in", color: "var(--primary)" },
  outPence: { label: "Money out", color: "var(--muted-foreground)" },
} satisfies ChartConfig
 
type Row = { month: string; inPence: number; outPence: number }
 
export function MoneyInOut({ data }: { data: Row[] }) {
  return (
    <ChartContainer config={chartConfig} className="min-h-[220px] w-full">
      <BarChart accessibilityLayer data={data} barGap={2}>
        <CartesianGrid vertical={false} />
        <XAxis dataKey="month" tickLine={false} axisLine={false} tickMargin={8} />
        <ChartTooltip
          content={
            <ChartTooltipContent
              indicator="line"
              // formatter replaces the whole row; name is the Bar's dataKey, which matches the config keys
              formatter={(value, name) => (
                <div className="flex w-full justify-between gap-4">
                  <span className="text-muted-foreground">
                    {chartConfig[name as keyof typeof chartConfig]?.label}
                  </span>
                  <span className="font-medium tabular-nums">{formatMoney(Number(value))}</span>
                </div>
              )}
            />
          }
        />
        <ChartLegend content={<ChartLegendContent />} />
        <Bar dataKey="inPence" fill="var(--color-inPence)" radius={2} />
        <Bar dataKey="outPence" fill="var(--color-outPence)" radius={2} />
      </BarChart>
    </ChartContainer>
  )
}

Because colour alone should not carry meaning, which is also WCAG 2.2 success criterion 1.4.1, the chart has a small legend with text labels and the tooltip names each series.

Transactions with receipt status

The table answers "what do I need to sort out", not just "what happened". Columns: date, payee, category, account, amount, receipt. Amounts are right-aligned with tabular figures, money out shows as a minus, and the receipt column says "Attached" or "Missing" in words, with a quiet badge for missing ones and a link to upload:

<td className="text-right tabular-nums">{formatMoney(tx.amountPence)}</td>
<td>
  {tx.receiptId ? (
    <span className="text-muted-foreground">Attached</span>
  ) : (
    <Link href={`/transactions/${tx.id}/receipt`} className="text-sm font-medium underline-offset-4 hover:underline">
      Missing, add receipt
    </Link>
  )}
</td>

formatMoney is the same Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP" }) helper from dashboard design for MVPs, so every amount on the screen formats the same way.

The visual system

The look comes from a handful of theme decisions, not from decoration:

  • One green accent, used for money in, the primary button and the active account. Nothing else is coloured, apart from a muted red for overdue bills.
  • Hairline borders in a light warm grey, instead of shadows, to separate areas.
  • 13px base type for tables and labels, with tabular figures everywhere numbers appear.
  • Left-aligned everything, with numbers right-aligned in their columns.
  • No gradients, no emoji, no icon circles. Icons appear only where they help recognition, such as the account type in the sidebar.
/* app/globals.css: the key tokens for this concept */
@theme {
  --text-sm: 0.8125rem;
  --text-sm--line-height: 1.25rem;
}
 
:root {
  --radius: 0.375rem;
  --primary: oklch(0.52 0.12 155);
  --primary-foreground: oklch(0.985 0 0);
  --border: oklch(0.92 0.005 85);
  --muted-foreground: oklch(0.5 0.01 85);
}

The code for applying these across shadcn/ui components is in how to make a shadcn app not look like shadcn.

Before and after, side by side

AreaBeforeAfter
HeaderPurple-to-blue gradient, emoji title, centredPlain page title, period selector, left-aligned
Top of pageFour identical stat cards with icon circlesCash runway as the headline, money in and out beside it
BalanceA stat cardAccounts in the sidebar, each with its balance
ChartSpending only, one colour per monthMoney in vs money out, one accent and one grey, tooltip in pounds
UpcomingNothingBills due, with date, payee, amount and account
TransactionsTransaction 1, 2, 3, dollar amountsReal payees, pounds, receipt status, action to add a receipt
TypeDefault 14px, everything centred13px, tabular figures, left-aligned
ColourGradient plus rainbowOne green accent, neutrals, muted red for overdue
StatesNoneEmpty, loading and error states for every panel

Lessons

  1. Start with the question, not the layout. "How long will the cash last" produced the runway figure, the in vs out chart and the bills list. "Finance dashboard" produced four cards.
  2. Most of the AI draft was usable. The components, the grid and the rough structure stayed. What changed was priority, content and a few theme values. That is often the case: redesigning an AI-built app does not have to mean starting again.
  3. Real data changes the design. Long payee names, refunds and missing receipts shaped the table more than any visual choice.
  4. Locale is design. Pounds, day-month dates and tabular figures are small details that tell users the product was made for them.
  5. Colour is a budget. One accent spent on the thing that matters reads as confident. Seven colours spent on nothing reads as generated.

The principles behind these are in dashboard design for MVPs and why every AI-built app looks the same, and the whole topic lives in the design guide for AI-built apps. The next teardown in the series will take a different kind of AI-built screen.

If you have an AI-built app that works but looks and feels like this before version, this is the kind of redesign I do, in your codebase, as a designer who writes the code. See FindPopups for a real product I designed and built.

Questions founders ask

Is this a real client's app?

No. It is an illustrative concept I designed to show the process. The before is a composite of what AI builders typically produce for a finance dashboard prompt, and the after is my redesign of it.

How long does a redesign like this take?

It depends on how many screens and how much real data plumbing is involved. The visual changes are quick once the decisions are made. Deciding what the screen is for takes longer, and matters more.

Can I make these changes inside Lovable, Bolt or v0?

Many of them, yes. Theme tokens, copy, currency formatting and layout changes can be prompted one at a time. Calculations like cash runway need real data and careful checking, which is easier in the code.

Ant

Product designer who builds MVPs, based in Nottingham. 10+ years designing digital products, and the only person at BlackFig. More about me