webdevpuneet.com

Tip Calculator — Free HTML CSS JS Snippet

Tip Calculator · Tools · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="wrap">
  <div class="card">
    <h2 class="heading">Tip Calculator</h2>
    <div class="field">
      <label class="label">Bill Amount</label>
      <div class="input-wrap">
        <span class="prefix">$</span>
        <input class="input" id="bill" type="number" min="0" step="0.01" placeholder="0.00" oninput="calc()" value="85.00">
      </div>
    </div>
    <div class="field">
      <label class="label">Tip Percentage</label>
      <div class="tip-btns">
        <button class="tip-btn" onclick="setTip(this,10)">10%</button>
        <button class="tip-btn active" onclick="setTip(this,15)">15%</button>
        <button class="tip-btn" onclick="setTip(this,18)">18%</button>
        <button class="tip-btn" onclick="setTip(this,20)">20%</button>
        <button class="tip-btn" onclick="setTip(this,25)">25%</button>
        <button class="tip-btn custom-btn" onclick="setCustom(this)">Custom</button>
      </div>
      <input class="custom-input" id="customTip" type="number" min="0" max="100" placeholder="Enter %" oninput="calc()" style="display:none">
    </div>
    <div class="field">
      <label class="label">Number of People</label>
      <div class="people-ctrl">
        <button class="ppl-btn" onclick="changePeople(-1)">&#x2212;</button>
        <span class="ppl-count" id="pplCount">2</span>
        <button class="ppl-btn" onclick="changePeople(1)">+</button>
        <span class="ppl-label">people</span>
      </div>
    </div>
    <div class="results">
      <div class="result-row">
        <div class="result-label">Tip Amount<span class="per-person">/ person</span></div>
        <div class="result-val" id="tipPerPerson">$6.38</div>
      </div>
      <div class="result-row">
        <div class="result-label">Total Amount<span class="per-person">/ person</span></div>
        <div class="result-val accent" id="totalPerPerson">$48.88</div>
      </div>
      <div class="divider"></div>
      <div class="result-row sm">
        <div class="result-label">Tip Total</div>
        <div class="result-val sm" id="tipTotal">$12.75</div>
      </div>
      <div class="result-row sm">
        <div class="result-label">Grand Total</div>
        <div class="result-val sm" id="grandTotal">$97.75</div>
      </div>
    </div>
    <button class="reset-btn" onclick="reset()">Reset</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

6 tip preset buttons with .active highlight toggle
Custom % input: hidden by default, shown on Custom button click
Live calculation: all 4 results update on every bill/tip/people change
changePeople() with Math.max(1) guard prevents going below 1 person
toFixed(2) on all monetary outputs for consistent decimal places
Tip per person and total per person as primary large-display results
Tip total and grand total as secondary smaller results
Reset function restores all state to defaults without page reload

About this UI Snippet

Tip Calculator — Preset Tips, Custom %, Bill Split, and Live Per-Person Totals

Screenshot of the Tip Calculator snippet rendered live

A tip calculator is a simple but highly-used utility component for restaurant billing, group dining (split the total with the split payment calculator), and service tip scenarios. This snippet provides a complete dark-themed tip calculator with a bill amount input, six tip preset buttons (10%, 15%, 18%, 20%, 25%, Custom), a people counter with a +/− stepper, and a results panel showing tip per person, total per person, tip total, and grand total — all updating live on every input change.

The tip percentage toggle system

The six tip buttons use a shared .active class to highlight the selected preset. setTip(btn, pct) removes .active from all buttons and adds it to the clicked one, then stores the selected percentage in the tipPct variable and calls calc(). The Custom button shows a hidden input field and sets up an inline oninput handler that reads the custom percentage and calls calc().

The bill split calculation

calc() reads the bill amount, multiplies by tipPct/100 to get the tip, adds to get the total, then divides both by the people count. All four output fields (tip/person, total/person, tip-total, grand-total) are updated in a single pass. toFixed(2) ensures two decimal places consistently.

The people counter

changePeople(delta) increments or decrements the people count, enforcing Math.max(1) so it cannot go below 1. The counter display and all result calculations update immediately.

The reset function

reset() clears the bill input, restores tipPct to 15, people to 2, re-applies the 15% active class, hides the custom input, and zeroes out all result displays. This pattern — resetting to known defaults rather than re-rendering — avoids a full component re-render in vanilla JS.

Input validation and edge cases

The bill input uses type="number" with min="0" and step="0.01" to prevent negative values and accept decimal inputs at the browser level. calc() applies parseFloat(value) || 0 as a safe fallback so that a blank or invalid input produces 0 rather than NaN. Division by the people count is always safe because changePeople() enforces a minimum of 1 via Math.max(1, people + delta). These three guards — || 0, min="0", and Math.max(1) — prevent all common calculation edge cases without a separate validation library.

Rounding and floating-point precision

toFixed(2) converts the calculated float to a two-decimal-place string, which is correct for currency display. However, floating-point arithmetic can produce values like 106.99999999 rather than 107.00. For financial calculations where exact rounding matters, multiply all values by 100 to work in integer cents, perform integer arithmetic, then divide by 100 at display time. For a tip calculator the visual difference is imperceptible, but in invoice or payment processing contexts always use integer cent arithmetic or a library like Decimal.js.

Step by step

How to Use

  1. 1
    Enter the bill amountType the bill total in the $ input field. All results update immediately as you type.
  2. 2
    Choose a tip percentageClick one of the six preset tip buttons (10%, 15%, 18%, 20%, 25%) or click Custom to enter your own percentage in the input that appears.
  3. 3
    Set the number of peopleUse the + and − buttons to set how many people are splitting the bill. The per-person totals update automatically.
  4. 4
    Read the resultsThe results panel shows tip per person and total per person (large), plus tip total and grand total (smaller). All four update live.
  5. 5
    Reset for a new calculationClick the Reset button to clear the bill, restore defaults (15% tip, 2 people), and zero the results.
  6. 6
    Export for your frameworkClick "JSX" for a React component using useState for bill, tipPct, and people. Click "Vue" for a Vue 3 SFC with reactive refs.

Real-world uses

Common Use Cases

Restaurant and dining app tip split utility
Embed in a food delivery or restaurant booking app as a post-meal utility. Pre-fill the bill amount from the order total. Add a "Share split" button that generates a payment link (Venmo or PayPal.me) with the per-person amount pre-filled.
Personal finance and budgeting app dining module
Include as a standalone utility page within a budgeting app. After calculation, offer an "Add to expenses" button that logs the user's share (total per person) to their dining expense category.
Event and group booking payment calculator
Adapt for group activities, shared taxi fares, or hotel room splits. Replace "Tip Percentage" with "Service Charge %" or "Extra Costs" to handle any group-split scenario beyond restaurant tipping.
Extend with currency selection and rounding options
Add a currency selector (USD, EUR, GBP, etc.) that switches the prefix symbol and formats the output with Intl.NumberFormat. Add a "Round up per person" toggle that rounds each person's share to the nearest dollar and shows the adjusted totals.
Study live form calculation patterns
The snippet demonstrates a stateless live-calc pattern: read input values → compute → write output values in a single function. This is the foundation for spreadsheet-like form UIs, pricing calculators, mortgage calculators, and any tool where inputs drive displayed results.
Freelancer invoice tip or service fee calculator
Repurpose for freelance services: the "bill amount" becomes the project fee, "tip %" becomes a "platform fee %" or "tax %", and the total becomes the client invoice amount. The split feature becomes useful for shared project billing between multiple clients.

Got questions?

Frequently Asked Questions

When Custom is clicked, setCustom() adds .active to that button and sets the hidden #customTip input to display: block. It then attaches an inline oninput handler that reads the entered percentage, stores it in tipPct, and calls calc(). The input disappears when a preset button is clicked via setTip(). To improve UX, call ci.focus() immediately after showing the input so the cursor lands in the field without an extra click. Add a min="0" max="100" constraint on the input element so browsers enforce the range at the HTML level. In the oninput handler, further clamp with Math.min(100, Math.max(0, parseFloat(ci.value) || 0)) to handle cases where the user pastes a value outside the valid range, preventing negative tip percentages or values above 100% from producing unexpected results.

Use Intl.NumberFormat: const fmt = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }); Then replace all "$" + value.toFixed(2) calls with fmt.format(value). For other currencies, pass currency: "EUR" etc.

Use const [bill, setBill] = useState(85); const [tipPct, setTipPct] = useState(15); const [people, setPeople] = useState(2). Compute derived values: const tip = bill * tipPct / 100; const total = bill + tip; const tipPP = tip / people; const totalPP = total / people. Render all four directly in JSX — no separate calc() call needed. Use useMemo to memoize the computed values if the component has many siblings re-rendering: const results = useMemo(() => ({ tip, total, tipPP, totalPP }), [bill, tipPct, people]). For the custom tip input, manage a separate const [customMode, setCustomMode] = useState(false) flag that shows or hides the custom input field and switches the tipPct to the custom value.

A people variable holds the split count and calc() divides both the tip and the total by it: tip / people goes to #tipPerPerson and (bill + tip) / people goes to #totalPerPerson, each formatted with toFixed(2). The full-table figures are written alongside to #tipTotal and #grandTotal, so the card always shows per-person and whole-bill numbers at once. The plus/minus stepper just increments or decrements people (clamped to a minimum of 1) and calls calc() again.

The presets are plain buttons that call setTip(this, pct) with the percentage inline — <button class="tip-btn" onclick="setTip(this,18)">18%</button> — so editing the numbers in the markup is the whole job. setTip() stores the value in tipPct, moves the .active class to the clicked button, hides the custom input, and re-runs calc(). Keep the button label and the argument in sync, and keep the Custom option last so users can still enter any value your presets don't cover.