HTMLKit

Moving between pages

Links between your pages are ordinary links:

<a href="/guide/install/">Install</a>

Once the first page has loaded, clicking one doesn't reload the browser tab. HTMLKit loads the next page's code and a small file of its data, builds the page in the browser, keeps the layout both pages share, and swaps in the new page. There's nothing to set up.

What stays and what changes

Part of the pageWhen you move to another page
A layout both pages useStays, with its state and module instance. An open menu stays open.
That layout's propsUpdate from its loader. <hk-nav> marks the new page current.
That layout's providersA changed selected input adopts compatible prepared data without repeating the read. An existing read for the same input stays current.
The page, and any layout only one page usesReplaced. A fresh page starts from its loader's state.
<title>, description, and other head declarationsUpdate to the new page's in one step, also inside a view transition.
Declared scriptsStay loaded. A script the next page declares loads once; none runs twice.
Scroll positionTop of the page, or the #fragment in the link. Back and forward return to where you were.
Keyboard focusStarts from the top, as after a page load.

Screen readers hear the new page's title. Back, forward, and reload work as usual, and every URL still loads directly.

Provider adoption keeps edited drafts and does not reconnect a retained layout. Its current browser values, subscriptions, pending reads, and handled failures remain authoritative for the same selected input, so returning to cached prerendered HTML does not replace newer live data. Change a selected input or explicitly refresh when the data should be read again.

Faster clicks

When the pointer rests on a link, the link gets keyboard focus, or a finger touches it, HTMLKit starts loading that page's code and data. By the click, it's usually ready. Moving across a menu fetches nothing. Links on screen only load code that several pages share, never a page's own content, so a long sidebar stays cheap.

Choose a different policy for the whole site in htmlkit.config.ts:

export default defineConfig({ prefetch: 'visible' });

Or for one link, or every link inside an element:

<a href="/big-report/" data-hk-prefetch="none">Big report</a>
<nav data-hk-prefetch="visible">…</nav>
prefetch / data-hk-prefetchLink on screenHover, focus, or touch
interaction (default)Shared codeThe page's code and data
visibleThe page's code and dataThe page's code and data
noneNothingNothing

visible suits small sites. Prefetching never runs a page's code in the browser; it only downloads it. For a page that renders on each request, loading its data runs its loaders on the server, with the link's query and the reader's cookies, as loading the page does. Give links to such pages data-hk-prefetch="none" when their loaders are costly.

Animating the change

Add the rule browsers use for animated page loads, and HTMLKit animates client navigation with it too:

@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}

The default is a cross-fade. Style ::view-transition-old(root) and ::view-transition-new(root) to change it. Firefox loads pages without the animation.

Showing that a page is loading

While a page loads in place, <html> has data-hk-navigating. While a form submits in place, the form also has data-hk-submitting and aria-busy="true". Both go when the next page has rendered, or when the navigation fails or another replaces it. Show a pending indicator with CSS:

.progress { opacity: 0; }
/* Shown after 150 ms, so a quick navigation shows nothing. */
:root[data-hk-navigating] .progress { opacity: 1; transition: opacity 200ms 150ms; }
form[data-hk-submitting] button { cursor: progress; opacity: 0.6; }

aria-busy tells screen readers the form is changing, and they hear the next page's title once it renders, so an indicator needs no announcement of its own. Scripts read the browser's own state: navigation.transition is set while a navigation is under way, and navigatesuccess and navigateerror fire when it ends.

Running code after a navigation

Controllers connect and disconnect with their components, so most code needs nothing else. For code that watches the whole page, such as analytics, listen to the browser's own event:

navigation.addEventListener('navigatesuccess', () => {
  track(location.pathname);
});

hk:ready fires once, when the first page has loaded.

Browser support

Client navigation needs the Navigation API with NavigateEvent.sourceElement: Chrome and Edge 135, Firefox 147, and Safari 26.2. Older browsers and readers without JavaScript load every page in full, with the same result.

In htmlkit dev, styles from pages you've visited stay loaded until the next full reload. Built sites remove a page's styles when you leave it.

Next: Configuration and API.

HTMLKit. MIT-licensed application framework for HTML Next components: routes, layouts, loaders, and static pages, published from nextwebwg/html-next. Prerelease: the proposals are at Stage 0 and may change.