HTML5

Software Mile builds on HTML5 and the modern web platform – the foundation under every web application, responsive site, and browser-based tool we deliver.

The Modern Web Platform

  • Responsive, accessible interfaces that work across devices and screen sizes
  • Progressive web apps that feel native in the browser, with offline capability
  • Canvas, media, and interactive features where the experience calls for them
  • Standards-based markup that stays maintainable and accessible

HTML5 is the substrate of everything on the web – we use it well, with accessibility built in. Tell us what you are building.

Do You Need a Single-Page Application, or Will Server-Rendered HTML Do?

This choice sets the shape of everything downstream. A single-page application brings a build pipeline, client-side routing, state management, bundle size to watch and a separate plan for anything that must be indexable. All are solvable, and each one keeps asking for attention long after launch.

If your application is mostly forms, tables, reports and navigation between pages, server-rendered HTML with modest JavaScript gets you there sooner and stays simpler to operate. A framework earns its place when the interface is genuinely stateful: live filtering across large sets, drag interactions, editors, anything where a page reload loses the user’s place. React is a good answer to that problem and a poor answer to a marketing site.

What Accessibility Requires Beyond Semantic Tags

Using the right elements is the foundation and not the whole job. Where accessibility is written into a contract it usually points at WCAG level AA; version 2.2 has been the current recommendation since 2023, and 2.1 still turns up in procurement language written earlier. Either way, meeting the standard is as much about how the interface behaves as about how it is marked up, so raise it at the start if it is contractual for you.

Automated scanners are useful and cover only part of this. The rest is found by navigating the interface with the keyboard and listening to it with a screen reader, which takes time that has to be in the estimate. Reviews tend to come back with the same findings:

  • Labels programmatically tied to inputs, not merely placed near them.
  • Focus that moves sensibly when dialogs open and close and views change.
  • Every interactive control reachable and operable by keyboard alone.
  • Contrast that holds up against your actual brand colors.

When Is a Progressive Web App Better Than a Native App?

A progressive web app avoids app store review, ships updates the moment you deploy, opens from a link and runs from one codebase. For internal tools, kiosks and anything distributed to a known group of users, that combination is hard to argue with, and a service worker can hold the interface together through short connectivity gaps.

It is the weaker choice when discovery happens in an app store, when you need deep operating system integration, or when you depend on device capabilities browsers expose inconsistently. Support for background behavior and notifications has been uneven between platforms, so verify the specific capability you need on the devices your users actually carry. Where a native build is warranted, that is what our mobile and cross-platform app development work covers.

What Actually Makes a Page Fast?

Perceived speed comes down to a few things, and they are rarely what teams argue about. Oversized images, render-blocking scripts, third-party tags and fonts that delay text are the usual causes. Core Web Vitals, Google’s published measures for loading, interaction responsiveness and layout stability, at least give everyone a shared number to point at instead of competing opinions.

Refactoring stylesheets will not offset an unoptimized hero image or five analytics scripts loading ahead of your content. The uncomfortable version of this conversation is usually about marketing tags, not code, and it goes better while the site is still being built.

Component Libraries Earn Their Keep on Multi-Team Products

A component library with agreed patterns for forms, tables, buttons and layout, plus tokens for spacing, type and color, stops the same interface being solved five ways. It also makes accessibility systemic: fix the component once and every screen that uses it improves, which beats correcting the same mistake across dozens of templates.

A brochure site does not need any of this. A product with several contributors and a multi-year life does, and adding it later means touching every template that already exists. The moment to decide is when the second team is about to start.

Support Is a Per-Feature Question, Not a Per-Browser One

There is no single answer to whether a browser "supports HTML5," because the platform ships feature by feature and always has. What a project actually picks is a support matrix, and it should be written down before the first component is built: which browsers, which minimum versions, which devices, and what supported means at each tier. A common split is full interactivity on current evergreen browsers and a readable, usable, plainer experience everywhere else, which is a cheaper promise to keep than pixel parity on everything.

The devices that set the floor are usually not laptops. Embedded WebViews inside other applications, kiosks and handheld scanners lag further, and managed enterprise images are sometimes pinned to a build several years old.

Test the capability, not the browser. The @supports rule in CSS and a property check in JavaScript, such as testing whether IntersectionObserver exists on window, tell you what is available at runtime, while user agent strings are frozen, spoofed and rewritten by corporate proxies, so reasoning from them ages badly. Polyfills are the other lever and they are not free, since the bytes usually ship to everyone including the browsers that never needed them; where the feature is decorative, a fallback path costs less. A handful of platform facts constrain the matrix more than any of the arguments about it:

  • Desktop browsers update themselves, so "current and previous version" is a workable line. On iOS the engine arrives with the operating system, which makes the phone's OS version the real constraint rather than the browser name.
  • A WebView takes its engine from the host platform and its update schedule from the app that embeds it, so an interface inside someone else's application can be several releases behind the standalone browser on the same device.
  • Feature detection belongs at the point of use. A capability table built once at startup drifts away from what the browser will actually do by the time the code reaches it.
  • Anything behind a permission prompt, including camera, location and notifications, can be fully implemented and still denied at runtime. A support check is not a success check, and both paths need a designed outcome.

The Form Behavior You Get Without Writing It

Forms are where the platform returns the most for the least code. The required, type, min, max, step, pattern and maxlength attributes give you constraint checking and browser-supplied error messaging with no JavaScript at all, and the Constraint Validation API exposes the same state to script through checkValidity(), setCustomValidity() and the :invalid and :user-invalid pseudo-classes. The usual reason teams replace the native messages is language and styling, since the default text comes from the browser, in the browser's language rather than your site's. Do that by setting novalidate on the form and rendering your own messages while leaving the attributes in place, so the semantics stay available to assistive technology and the API keeps reporting validity for you.

None of it is a security control. Client-side validation is a courtesy to the person typing, and every rule that matters has to be enforced again on the server, because a request can be composed without a browser in the loop at all.

The attributes that most change how a form feels on a phone cost nothing to add:

  • type="email", type="url" and type="tel" bring up the matching on-screen keyboard. Only email and url carry a format check; tel deliberately has none, because national formats vary too much to validate generically.
  • inputmode="numeric" gets the numeric keypad and inputmode="tel" gets the dial pad. For values that are digit strings rather than quantities, such as ZIP codes, order numbers and card numbers, prefer type="text" with an inputmode over type="number", which brings spinners, scroll wheel changes and locale parsing you do not want.
  • autocomplete tokens such as given-name, email, street-address and one-time-code let the browser and the user's password manager fill fields correctly, and are what the Identify Input Purpose criterion added in WCAG 2.1 asks for. Setting autocomplete="off" on an address or payment field mostly just makes people type more.
  • enterkeyhint changes the label on the on-screen return key to search, send, next or done. It is a small thing that removes a lot of hesitation on a long form.
  • A real form element with a submit button gets Enter to submit, autofill and the browser's own restore behavior. A div with a click handler gets none of the three and has to reimplement each one.
  • type="date" and type="time" render the platform's native picker, which you cannot restyle. If the design specifies a particular calendar, that is a custom component with its own keyboard and screen reader work, not a CSS exercise.

How a Service Worker Update Actually Reaches the User

The deploy itself lands immediately, with nothing between your build and your users the way a store review sits in front of a native release. What decides when an already-running tab picks that build up is the service worker's own update cycle, and it is worth agreeing on before anyone promises instant. The browser refetches the worker script on navigation, installs the new one alongside the active one, then holds it in a waiting state until every tab the old worker controls has been closed. A reload does not release it, because a reload never leaves the page uncontrolled.

You choose the handoff rather than inherit it. Calling skipWaiting() during install together with clients.claim() on activate makes the new worker take over at once, which is what you want for a kiosk or a wallboard nobody is typing into. The alternative is to detect the waiting worker and offer an explicit "new version ready, reload" control, which is better wherever someone may have a half-finished form open. Both are defensible; drifting into one by accident is not.

The expensive mistake is a cache-first strategy over the HTML entry document with cache names that never change. The app then serves an old index that asks for hashed script files no longer on the server, and the user gets a blank page and a 404 rather than anything recoverable. Network-first or stale-while-revalidate for navigation requests, cache-first only for content-hashed static assets, and a cache name carrying the build identifier so activation can delete the previous one, keeps that from happening.

When someone reports seeing an old build, the diagnosis is quick:

  • Reloading with the devtools option to bypass the service worker for network requests shows the current build: the worker was serving the stale copy.
  • Unregistering the worker and reloading fixes it: same cause, confirmed, and the caching strategy for navigation requests is what to look at.
  • It survives unregistering and a cleared reload: the stale copy is in the HTTP cache or at the CDN, so the fix is Cache-Control on the entry document and a purge, not worker code.
  • Only some people see it: their tab was open across the deploy, so the new worker is installed and waiting rather than active.

Offline Capability Is a Storage Decision, and None of the Stores Are Durable

Everything a browser keeps locally can go away. Browsers evict under storage pressure, users clear site data, and a private window discards it when the session ends. Safari's tracking prevention adds a firmer rule: script-written localStorage and IndexedDB are cleared after seven days without user interaction with the site, so an installed tool used every other week can find its local state gone. navigator.storage.persist() asks for an exemption and the browser is free to refuse, while navigator.storage.estimate() reports quota and current usage. Treat local data as a cache and a buffer, never as the record of truth.

Queued writes turn reconnection into a conflict problem, and that decision is a product one before it is a technical one. Last write wins, server always authoritative, and per-field merge behave very differently the first time two people edit the same record from two devices, and someone has to decide what the user sees when a change they made is rejected minutes later. This is the part that gets deferred and then discovered in testing.

Which store to use follows from the shape of the data and how often it is read:

  • localStorage is synchronous, strings only, and roughly 5 MB. Every read and write blocks the main thread, so it suits small preferences and nothing inside a loop. sessionStorage behaves identically but is scoped to one tab and cleared when that tab closes.
  • IndexedDB is asynchronous, stores structured values with indexes, and has room for orders of magnitude more. It is the right place for a records cache or an outbox, and its API is verbose enough that a thin wrapper usually pays for itself.
  • The Cache Storage API holds Request and Response pairs and is what a service worker uses for assets and API responses. It is separate from the HTTP cache and is not cleared alongside it.
  • Cookies are the only one of these sent to the server on every matching request, so application data there costs bandwidth on every call, and the practical ceiling is about 4 KB per cookie.

What the Browser Boundary Costs When You Call Existing Services

A browser is not a general purpose HTTP client, and the same-origin policy is where most integrations first stall. An origin is scheme plus host plus port, so https://app.example.com and https://api.example.com are different origins, and so is the same host reached on a different port. Cross-origin reads are blocked unless the server opts in.

The opt-in is CORS, and the preflight rule is what catches people out. Only GET, HEAD and POST with a content type of application/x-www-form-urlencoded, multipart/form-data or text/plain skip the preflight, so an ordinary JSON POST does trigger one: Content-Type: application/json is enough on its own, as is any custom header such as Authorization. The browser sends an OPTIONS request first and the server has to answer it with the permitted origin, methods and headers before the real request goes out. The response to the actual request must then name your origin in Access-Control-Allow-Origin, and if the session rides on cookies it must also send Access-Control-Allow-Credentials: true, where the wildcard * is not accepted.

Cookie sessions bring SameSite with them. A cookie that has to travel to a different site needs SameSite=None; Secure, which requires HTTPS and leaves you exposed to browser rules on cross-site cookies that keep tightening. Putting the application and the API behind one origin with a reverse proxy removes the whole category: no preflight, no SameSite=None, one certificate, one place to set security headers. If your infrastructure already enforces a Content-Security-Policy, ask for the policy early, because inline scripts and styles will then need nonces or hashes and some third-party embeds do not fit one without changes.

For server-initiated updates, pick by direction and frequency rather than by novelty. Server-sent events are one-directional HTTP with automatic reconnection and no special infrastructure, though over HTTP/1.1 each open stream occupies one of the roughly six connections a browser allows per host. WebSocket is bidirectional but needs every load balancer and proxy in the path to pass the upgrade, plus your own heartbeat and reconnect handling. Polling on a sensible interval is still the correct answer for anything that changes every few minutes.

Frequently Asked Questions

Is HTML5 still a meaningful term?

It names the modern web platform rather than a version you can target. HTML stopped being a numbered specification: since the 2019 agreement between W3C and WHATWG there is one living standard, maintained continuously, with no next number to wait for. In a requirements document, HTML5 usually means the current platform as a whole, semantic elements, forms, canvas, audio and video, plus the JavaScript APIs around them. It is worth confirming which of those a given requirement actually refers to, because the answers differ a lot.

What has to exist before the work starts?

A short list: where the data comes from and who owns it, brand colors and typefaces, the hosting or deployment target along with whoever controls DNS and TLS there, and a named person who can settle interface questions without convening a committee. Work can start on the parts that do not depend on the open items, but open items have a habit of becoming the schedule. Naming the owner of each one at the start is usually more useful than trying to close them all first.

Does a progressive web app work offline on its own?

No. A web app manifest makes it installable and controls the icon, name and display mode, while offline behavior comes only from a service worker and the caching strategy written into it. Both require a secure context, so the site has to be served over HTTPS, with localhost exempted for development. Deciding what genuinely has to work offline, and what should honestly refuse until the connection returns, is the first design question rather than the last.

What do you need to know about our existing API?

Two things above all: how it authenticates, and whether the contract can change. Cookie sessions, bearer tokens and an external SSO provider each pull the front end in a different direction, and with SSO someone holding administrative access has to register the application and its redirect URIs before a login can be tested at all. If the API is fixed, the browser absorbs whatever shape it has and some of that becomes glue code; if it can change, adjusting one response so a screen needs a single call instead of four is usually the cheaper fix.

Do we still need to support Internet Explorer?

Assume not, unless one specific system requires it. Microsoft retired the Internet Explorer 11 desktop application on most Windows 10 versions on June 15, 2022, and IE mode inside Edge is the remaining supported path for legacy internal sites. If something you own runs only there, treat it as a scoped exception for that application rather than a constraint on the whole project, since applying it project-wide changes what can be used on every other screen.

What does the finished front end need to run on?

It depends on the rendering choice. A client-rendered application is static files and can sit on any static host or CDN with no application server, while server-rendered HTML needs a runtime process, which is something to monitor, patch and scale. Both need a domain, a TLS certificate, and a deployment path that someone owns and can run without us. If you already have a hosting standard or an approved platform, say so at the start, because it constrains the rendering choice more than the rendering choice constrains it.

Will a client-rendered app preview and index correctly when someone shares a link?

Not by default. Google renders JavaScript in a deferred second pass, but most other crawlers and nearly every link preview scraper, including chat apps, social networks and messaging clients, read the HTML the server returns and never execute scripts. Titles, descriptions and Open Graph tags therefore have to be present in that first response, which means server-side rendering, prerendering, or at minimum a server-generated document head for shareable URLs. Decide which URLs matter for sharing early, because adding this later reaches into routing.