<output> marks the result of a calculation or user action: a total price, a slider's current value, a "Saved" confirmation. Its own attributes are for, the ids of the controls that feed the result; form, to join a form elsewhere in the page; and name, for form.elements. It holds plain text and is not submitted.
What makes it better than a <span> is its implicit ARIA role status, a polite live region: when its text changes, screen readers announce the new value without moving focus away from the slider being dragged.
<style>
body { font: 15px/1.6 system-ui, sans-serif; margin: 12px; }
output { font-weight: 700; color: #1565c0; }
input[type=number] { width: 7em; font: inherit; }
</style>
<form id="tip">
<label>Bill <input type="number" id="bill" name="bill" value="84.50" step="0.01"></label><br>
<label>Tip <input type="range" id="pct" name="pct" min="0" max="30" value="18"></label>
<output name="pctOut" for="pct">18%</output><br>
Tip <output name="tipOut" for="bill pct">$15.21</output>,
total <output name="total" for="bill pct">$99.71</output>
</form>
<script>
const form = document.getElementById('tip');
const money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
function update() {
const { bill, pct, pctOut, tipOut, total } = form.elements;
const tip = bill.valueAsNumber * pct.valueAsNumber / 100 || 0;
pctOut.value = `${pct.value}%`;
tipOut.value = money.format(tip);
total.value = money.format(bill.valueAsNumber + tip || 0);
}
form.addEventListener('input', update); // one listener catches every control
form.elements.pct.value = 25; // simulate the user dragging the slider
update();
</script>
One input listener on the form serves every control, because input events bubble. valueAsNumber avoids the bug where "84.50" + tip concatenates strings, and || 0 hides the NaN of an empty field. The text initially inside each <output> (the 18% values) is what users see before the script loads.
If the server needs the computed value, copy it into an <input type="hidden"> and recalculate it on the server anyway: client-side results can be tampered with. For a gauge or task progress, use <meter> or <progress> (Progress and Meter).