Source Code

<div class="msb-phone">
  <div class="msb-screen">
    <div class="msb-status"><span>9:41</span><span class="msb-batt"><i></i></span></div>

    <header class="msb-head">
      <button class="msb-back" aria-label="Back">&#8249;</button>
      <b>Split Bill</b>
      <span class="msb-spacer"></span>
    </header>

    <div class="msb-body">
      <div class="msb-total-card">
        <span class="msb-total-label">Total bill</span>
        <span class="msb-total" id="msbTotal">$96.40</span>
      </div>

      <div class="msb-mode">
        <button class="msb-mode-btn active" data-mode="equal">Split Equally</button>
        <button class="msb-mode-btn" data-mode="custom">Custom Amounts</button>
      </div>

      <div class="msb-people" id="msbPeople"></div>

      <button class="msb-add-person" id="msbAddPerson">+ Add person</button>

      <div class="msb-tip-card">
        <span class="msb-tip-label">Tip</span>
        <div class="msb-tip-options" id="msbTipOptions">
          <button class="msb-tip-opt" data-tip="0.15">15%</button>
          <button class="msb-tip-opt active" data-tip="0.18">18%</button>
          <button class="msb-tip-opt" data-tip="0.20">20%</button>
          <button class="msb-tip-opt" data-tip="0.25">25%</button>
        </div>
      </div>

      <div class="msb-summary" id="msbSummary"></div>
    </div>

    <div class="msb-footer">
      <button class="msb-request-btn" id="msbRequestBtn">Request Payments</button>
    </div>
  </div>
</div>

Mobile Split Bill Screen — Free HTML CSS JS Snippet

Mobile Split Bill Screen · Mobile · Plain HTML, CSS & JS · Live preview

What's included

Features

Equal and custom split modes share one people array, never duplicated data
Switching into custom mode pre-fills sensible equal-share starting values
Live balance check compares custom amounts against the real bill with rounding tolerance
Request button disables and relabels itself when custom amounts do not add up
Tip percentage selection recalculates tip, per-person tip, and grand total together
Adding or removing a person immediately updates every derived total for the active mode
Colored initials avatars generated per person for quick visual distinction
Zero dependencies, vanilla JavaScript only

About this UI Snippet

Mobile Split Bill Screen — Equal/Custom Modes with a Live Balance Check

Screenshot of the Mobile Split Bill Screen snippet rendered live

Splitting a bill among friends usually starts as "just split it evenly" and turns into "actually Jordan had the extra appetizer" halfway through. This snippet handles both cases in one screen — an equal-split mode that divides the total automatically, and a custom mode with per-person editable amounts that must add back up to the actual bill before the request button will let you continue.

Two modes sharing one people array

mode is either 'equal' or 'custom', and renderPeople() branches on it: equal mode renders a plain computed $X.XX amount for everyone (billTotal / people.length), while custom mode renders an editable number input per person bound to that person's own custom field. Switching modes never touches a separate data structure — the same people array backs both views.

Switching into custom mode seeds sensible defaults

When a user switches to custom mode with no amounts entered yet, the mode button handler pre-fills every person's custom value with an equal share, computed the same way equal mode would have shown it. This means starting from "everyone pays evenly" and then adjusting one or two amounts is the natural first move, rather than starting from a blank $0.00 for every person.

A live balance check that gates the primary action

customSum() totals every person's custom value, and renderSummary() compares that sum against billTotal (with a small tolerance for floating-point rounding). If they don't match, a warning line states exactly how much is unassigned or over, and the "Request Payments" button disables itself and relabels to "Balance Amounts to Continue" — this prevents sending payment requests that don't actually add up to the real bill.

Tip selection recalculates the whole summary, not just one line

Clicking a tip percentage button updates tipRate and re-runs renderSummary(), which recomputes the tip amount, the per-person tip share, and the grand total together from the one rate — so changing the tip percentage never leaves the total or per-person figures stale relative to the newly selected rate.

Adding and removing people keeps everything derived

addPersonBtn pushes a new guest into the people array and calls renderAll(); removing a person splices them out and does the same. In equal mode this immediately changes everyone's computed share since it divides by the current people.length; in custom mode, the balance warning immediately reflects however far the remaining custom amounts now are from the total.

Wiring it to real payment requests

Replace the setTimeout-based "Requests Sent!" confirmation with real calls to a payments API (Venmo, PayPal, Stripe, or an in-app wallet) for each person's computed share, and pull the initial billTotal from an actual scanned or entered receipt rather than the hardcoded demo value.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than tracing the equal/custom mode branching and balance-check logic by hand, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how renderPeople() and renderSummary() derive every displayed amount from the same people array and mode variable, and how the balance check gates the Request Payments button using a rounding tolerance to avoid floating-point false positives. The same assistant can help you optimize it, for instance asking whether splitting by percentage shares instead of raw dollar amounts would make custom mode easier to balance for larger groups. It is also useful for extending the screen: ask it to wire in a real payments API for the request button, support splitting specific line items (rather than only the total) among a subset of people, or add a receipt-scanning flow that populates billTotal automatically. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a mobile "split bill" screen in plain HTML, CSS, and JavaScript, framed inside a CSS phone mockup, no library.

Requirements:
- A total bill amount displayed prominently, a toggle between "Split Equally" and "Custom Amounts" modes, and a list of people (with add and remove controls) all backed by one shared JavaScript array of person objects, not separate data per mode.
- In equal mode, every person must show the same computed share (bill total divided by the number of people), automatically recalculating whenever a person is added or removed.
- In custom mode, every person must show an editable number input for their own amount, pre-filled with an equal share as a starting point the first time custom mode is entered with no amounts set.
- A live validation check must compare the sum of all custom amounts against the actual bill total (allowing a small tolerance for floating-point rounding) and, when they don't match, show a message stating exactly how much is unassigned or over, while disabling and relabeling the primary "Request Payments" button until the amounts balance.
- A row of tip percentage options (e.g. 15%, 18%, 20%, 25%) that, when selected, recalculates and displays the tip amount, the per-person tip share, and the grand total together in a summary section, all derived from the one selected tip rate.
- Clicking "Request Payments" when balanced must show a brief confirmation state before reverting.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA bill total and three people render in Split Equally mode, each showing the same computed share.
  2. 2
    Switch to Custom AmountsEach person now shows an editable amount input, pre-filled with an equal share to start from.
  3. 3
    Edit an amountThe summary and the "Request Payments" button update live, disabling with a warning if the amounts no longer add up to the bill.
  4. 4
    Add or remove a personTap "+ Add person" or a person's remove button — totals recompute immediately for the current mode.
  5. 5
    Pick a tip percentageThe tip amount, per-person tip share, and grand total in the summary recalculate together.
  6. 6
    Wire it to real paymentsReplace the demo confirmation with real API calls to a payments provider for each computed share.

Real-world uses

Common Use Cases

Restaurant and group-dining payment apps
The canonical use case — splitting a real check between friends, including uneven splits for shared appetizers or different orders.
Travel and trip-expense apps
Adapt the same equal/custom split pattern for splitting a shared Airbnb, group activity, or trip expense among travelers.
Roommate and shared-household expense apps
Reuse the balance-check pattern to ensure recurring shared bills (rent, utilities) are always fully assigned before requesting payment.
Teaching derived-totals and validation UI
A concrete reference for gating a primary action behind a computed validation check, rather than letting an inconsistent state proceed.
Related: Mobile Checkout Screen
See the Mobile Checkout Screen for a related payment-flow mobile screen worth comparing against this one.
Related: Mobile Referral & Invite Friends Screen
See the Mobile Referral & Invite Friends Screen for a related social mobile-money screen worth pairing with this one.

Got questions?

Frequently Asked Questions

renderPeople() divides billTotal by people.length and displays the same computed value for every person whenever mode is "equal" — there is no per-person stored amount used in this mode, so adding or removing a person immediately changes everyone's displayed share.

customSum() totals every person's custom field, and renderSummary() compares it against billTotal with a small rounding tolerance. If they differ, a warning states exactly how much is unassigned or over, and the Request Payments button disables itself and relabels to "Balance Amounts to Continue".

When switching into custom mode with no custom amounts entered yet, the mode-switch handler seeds every person's custom field with an equal share as a sensible starting point, so adjusting one or two people's amounts is the natural first edit rather than filling in every field from scratch.

Clicking a tip option updates the single tipRate variable and re-runs renderSummary(), which recomputes the tip amount, the per-person tip share, and the grand total together from that one rate, so all three figures always reflect the currently selected percentage.

No — it is a UI demo. The button shows a "Requests Sent!" confirmation for a moment before reverting. Wire it to a real payments API (such as Venmo, PayPal, Stripe, or an in-app wallet) to actually send payment requests for each computed share.

Yes. Track people, mode, and tipRate in state, derive each person's displayed amount and the balance-check warning in a computed/useMemo value based on mode, and disable the request button from that same derived balanced boolean.