Skip to content

Building the UI

The app logic is complete. This lesson adds the CSS that makes BudgetBuddy readable and usable, then walks through a complete end-to-end test of every feature.

*, *::before, *::after { box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
background: #f5f5f5;
color: #1a1a1a;
margin: 0;
padding: 0;
}
header {
background: #2c4a1e;
color: white;
padding: 1rem 2rem;
}
header h1 {
margin: 0;
font-size: 1.5rem;
}
main {
max-width: 720px;
margin: 2rem auto;
padding: 0 1rem;
display: grid;
gap: 1.5rem;
}
/* ── Add Expense form ─────────────────────────────── */
.add-expense {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 1px 4px rgba(0,0,0,.08);
}
.add-expense h2 {
margin: 0 0 1rem;
font-size: 1.1rem;
}
#expense-form {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .75rem;
}
.field {
display: flex;
flex-direction: column;
gap: .25rem;
}
.field label {
font-size: .85rem;
font-weight: 600;
color: #555;
}
.field input,
.field select {
padding: .5rem .75rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1rem;
}
.field input:focus,
.field select:focus {
outline: 2px solid #2c4a1e;
outline-offset: 1px;
}
#expense-form button[type="submit"] {
grid-column: 1 / -1;
padding: .65rem 1.5rem;
background: #2c4a1e;
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
font-weight: 600;
}
#expense-form button[type="submit"]:hover {
background: #3a6328;
}
/* ── Summary bar ──────────────────────────────────── */
.expense-summary {
background: white;
border-radius: 8px;
padding: 1rem 1.5rem;
box-shadow: 0 1px 4px rgba(0,0,0,.08);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: .75rem;
}
.totals {
font-size: 1.1rem;
}
#total-amount {
font-weight: 700;
font-size: 1.3rem;
color: #2c4a1e;
}
.currency-controls,
.filter-controls {
display: flex;
align-items: center;
gap: .5rem;
font-size: .9rem;
}
/* ── Expense list ─────────────────────────────────── */
.expense-list {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 1px 4px rgba(0,0,0,.08);
}
.expense-list h2 {
margin: 0 0 .75rem;
font-size: 1.1rem;
}
#expense-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: .5rem;
}
.expense-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: .75rem 1rem;
background: #fafafa;
border: 1px solid #eee;
border-radius: 6px;
}
.expense-main {
display: flex;
align-items: center;
gap: .5rem;
}
.expense-desc {
font-weight: 500;
}
.badge {
font-size: .75rem;
padding: .2rem .5rem;
background: #e8f0e3;
color: #2c4a1e;
border-radius: 999px;
font-weight: 600;
}
.expense-meta {
display: flex;
align-items: center;
gap: .75rem;
}
.expense-date {
font-size: .85rem;
color: #777;
}
.expense-amount {
font-weight: 600;
min-width: 4rem;
text-align: right;
}
.btn-delete {
border: none;
background: none;
color: #c00;
cursor: pointer;
font-size: .9rem;
padding: .25rem;
border-radius: 4px;
line-height: 1;
}
.btn-delete:hover {
background: #fee;
}
.empty-state {
text-align: center;
color: #888;
padding: 2rem 0;
font-style: italic;
}
/* ── Responsive ───────────────────────────────────── */
@media (max-width: 540px) {
#expense-form {
grid-template-columns: 1fr;
}
.expense-item {
flex-direction: column;
align-items: flex-start;
gap: .4rem;
}
}

Work through every feature and confirm each one:

Add expenses:

  • Fill in the form and submit — the new expense appears in the list
  • The total updates to include the new expense
  • The form resets after submission
  • Submitting with an empty description shows an error alert
  • Submitting with amount 0 or negative shows an error alert

Delete expenses:

  • Click the ✕ button on any expense — it disappears from the list
  • The total updates after deletion

Persistence:

  • Add an expense, refresh the page — it is still there
  • Delete an expense, refresh — it remains deleted

Filter:

  • Select a category filter — only that category’s expenses appear
  • Select “All categories” — all expenses reappear
  • The total reflects only the visible (filtered) expenses

Currency conversion:

  • Select EUR — the total updates to the EUR equivalent
  • Select JPY — the total displays as a whole number with ¥
  • Switch back to USD — the original total is restored

Empty state:

  • Delete all expenses — the “No expenses yet” message appears
  • Add an expense — the message disappears

If every item passes, BudgetBuddy is complete.

  1. Paste style.css into your project. Reload and confirm the app looks presentable.
  2. Work through the complete checklist above. Fix any failures before moving on.
  3. Open DevTools → Console — confirm there are no uncaught errors.
  4. Open DevTools → Application → Local Storage — confirm expense data is stored under budgetbuddy_expenses.
  5. Open DevTools → Network — confirm the exchange rate API call fires once per session.
  • system-ui, sans-serif gives the app a native feel on every platform with no external font request.
  • CSS Grid handles the two-column form layout and collapses to one column on small screens.
  • The empty state message uses hidden toggled by renderExpenses — no JavaScript string comparison needed.
  • A complete feature test before shipping is not optional — catch failures now, not in production.