A tiny HTML proposal with two switches and one render function
A proposal can look perfectly reasonable until you change one option. The price updates, but the delivery time still belongs to the old plan. Now the page is giving two different answers. That is the small problem this example tackles: two packages, two revisions, and one consistent summary. It uses

A proposal can look perfectly reasonable until you change one option. The price updates, but the delivery time still belongs to the old plan. Now the page is giving two different answers. That is the small problem this example tackles: two packages, two revisions, and one consistent summary. It uses ordinary HTML and JavaScript. The company details and estimates are fictional. The useful part is the shape of the state. You can reuse it for a quote calculator, a package comparison, or a project scope preview. There are only two choices: let revision = "first"; let plan = "essential"; The estimate is derived from those choices. It does not need another state variable. const proposals = { first: { essential: { price: "$4,800", weeks: 3, items: ["Five responsive pages", "Contact form"] }, complete: { price: "$7,200", weeks: 4, items: ["Five responsive pages", "Contact form", "Project gallery"] } }, revised: { essential: { price: "$5,400", weeks: 4, items: ["Five responsive pages", "Booking flow", "Accessibility review"] }, complete: { price: "$8,100", weeks: 5, items: ["Five responsive pages", "Booking flow", "Accessibility review", "Project gallery"] } } }; Each combination has a complete record. That makes it possible to inspect the numbers and scope together before touching the interface. These are fixed example estimates. A real pricing tool would need its own rules for currency, tax, and calculations. Here is the markup. Put it in the body of an HTML page, with the JavaScript below it in a script element. <h1>Website proposal</h1> <p>Fictional estimates for a scope comparison.</p> <div role="group" aria-label="Proposal revision"> <button type="button" data-revision="first" aria-pressed="true">First draft</button> <button type="button" data-revision="revised" aria-pressed="false">Revised scope</button> </div> <div role="group" aria-label="Choose a package"> <button type="button" data-plan="essential" aria-pressed="true">Essential</button> <button type="button" data-plan="complete" aria-pressed="false">Complete</button> </div> <p id="summary" aria-live="polite" aria-atomic="true"></p> <ul id="deliverables"></ul> Both sets of buttons change a choice and call the same function: function render() { const proposal = proposals[revision][plan]; const revisionLabel = revision === "first" ? "First draft" : "Revised scope"; const planLabel = plan === "essential" ? "Essential" : "Complete"; document.getElementById("summary").textContent = `${revisionLabel}, ${planLabel}: ${proposal.price}; ${proposal.weeks} weeks.`; document.getElementById("deliverables").replaceChildren( ...proposal.items.map(item => { const li = document.createElement("li"); li.textContent = item; return li; }) ); document.querySelectorAll("[data-revision]").forEach(button => { button.setAttribute("aria-pressed", String(button.dataset.revision === revision)); }); document.querySelectorAll("[data-plan]").forEach(button => { button.setAttribute("aria-pressed", String(button.dataset.plan === plan)); }); } document.querySelectorAll("[data-revision]").forEach(button => { button.addEventListener("click", () => { revision = button.dataset.revision; render(); }); }); document.querySelectorAll("[data-plan]").forEach(button => { button.addEventListener("click", () => { plan = button.dataset.plan; render(); }); }); render(); The click handlers are deliberately boring. They do not calculate prices, rewrite individual list items, or decide which buttons should look selected. Those changes happen together in render(). Selecting Complete and then Revised scope keeps Complete selected. Switching the revision does not quietly reset the package. That behavior follows directly from keeping the choices separate. Using textContent also means a deliverable is inserted as text, rather than interpreted as HTML. If this later reads external data, that is a useful boundary to preserve. There are four expected results: Revision Package Price Delivery First draft Essential $4,800 3 weeks First draft Complete $7,200 4 weeks Revised scope Essential $5,400 4 weeks Revised scope Complete $8,100 5 weeks Checking each row catches incorrect data. Checking a sequence catches stale UI. Try Complete β Revised scope β Essential β First draft, and compare the summary, deliverables, and pressed button states after each step. Then use only Tab, Enter, and Space. Native buttons provide the keyboard interaction; aria-pressed exposes the selected state. The polite live region makes the combined price and delivery summary available to assistive technology. That is a starting point, and it still needs testing with the screen readers and browsers your audience uses. Both revisions in this page are already in the JavaScript object. The switch previews them. It does not save anything, publish a new version, or update a hosted page. That distinction matters when sharing a prototype. Label the control as a preview, keep the estimates fictional, and test the actual public URL after deployment. A working local file cannot tell you whether the recipient's link works. The page stays small because it has a small job: let someone compare scope without allowing the price, timeline, and deliverables to drift apart. AI disclosure: This article and the adapted code example were generated by an AI agent from an existing HTML example. The example was checked in a browser before publication.
Key Takeaways
- β’A proposal can look perfectly reasonable until you change one option
- β’This story was reported by Dev.to, covering developments in the dev space.
- β’AI advancements continue to reshape industries β read the full article on Dev.to for complete coverage.
π Continue reading the full article:
Read Full Article on Dev.to βShare this article



