Plain Vanilla Sites

#

Pages

For content-driven websites with low interactivity a multi-page approach is best suited.

Abandoning the use of frameworks means writing out those HTML pages from scratch. For this it is important to understand what a good minimal HTML page should look like.

An explanation of each element:

<!doctype html>
The doctype is required to have the HTML parsed as HTML5 instead of an older version.
<html lang="en">
The lang attribute is recommended to make sure browsers don't misdetect the language used in the page.
<head><title>
The title will be used for the browser tab and when bookmarking, making it effectively non-optional.
<head><meta charset="utf-8">
This is borderline unnecessary, but just to make sure a page is properly interpreted as UTF-8 this line should be included. Obviously the editor used to make the page should equally be set to UTF-8.
<head><meta name="viewport">
The viewport meta is necessary to have mobile-friendly layout.
<head><link rel="stylesheet" href="index.css">
By convention the stylesheet is loaded from <head> in a blocking way to ensure the page's markup does not have a flash of unstyled content.
<body><noscript>
Because web components don't work without JavaScript it is good practice to include a noscript warning to users that have JavaScript disabled. This warning only needs to be on pages with web components. If you don't want to show anything except the warning, see the template pattern below.
<body><header/main/footer>
The page's markup should be organized using HTML landmarks. Landmarks when used properly help organize the page into logical blocks and make the page's structure accessible. Because they are standards-based, compatibility with present and future accessibility products is more likely.
<body><script type="module" src="index.js">
The main JavaScript file is at the end, and will bootstrap the web components as explained on the components page.

Pages that should only show their contents if JavaScript is enabled can use this template pattern:

Semantics matter

The markup in the page should default to using semantic HTML, to improve accessibility and SEO. Web components should only be used in those cases where the level of complexity and interaction exceeds the capabilities of plain HTML markup.

Familiarize yourself with these aspects of semantic HTML:

Landmarks
As mentioned above, landmarks are the backbone of a page's structure and deliver good structure and accessibility by default.
Elements
Being familiar with HTML's set of built-in elements saves time, both in avoiding the need for custom elements and in easing implementation of the custom elements that are needed. When properly used HTML elements are accessibly by default.
Forms
HTML's built-in forms can implement many interactivity use cases when used to their full extent. Be aware of capabilities like rich input types, client-side validation and UI pseudo classes. When a suitable form input type for a use case cannot be found, consider using form-associated custom elements but be aware of the browser support for ElementInternals.

Favicons

There is one thing that you will probably want to add to the HTML that is not standards-based and that is a reference to a favicon:

#

Project

A suggested project layout for a vanilla multi-page website:

/
The project root contains the files that will not be published, such as README.md, LICENSE or .gitignore.
/public
The public folder is published as-is, without build steps. It is the whole website.
/public/index.html
The main landing page of the website, not particularly different from the other pages, except for its path.
/public/index.[js/css]
The main stylesheet and javascript. These contain the shared styles and code for all pages. index.js loads and registers the web components used on all pages. By sharing these across multiple HTML pages unnecessary duplication and inconsistencies between pages can be avoided.
/public/pages/[name].html
All of the site's other pages, each including the same index.js and index.css, and ofcourse containing the content directly as markup in the HTML, leveraging the web components.
/public/components/[name]/
One folder per web component, containing a [name].js and [name].css file. The .js file is imported into the index.js file to register the web component. The .css file is imported into the global index.css or in the shadow DOM, as explained on the styling page.
/public/lib/
For all external libraries used as dependencies. See below for how to add and use these dependencies.
/public/styles/
The global styles referenced from index.css, as explained on the styling page.

Configuration files for a smoother workflow in programmer's editors also belong in the project's root. Most of the development experience of a framework-based project is possible without a build step through editor extensions. See the Visual Studio Code setup for this site for an example.

#

Routing

The old-school routing approach of standard HTML pages and <a> tags to link them together has the advantages of being easily indexed by search engines and fully supporting browser history and bookmarking functionality out of the box. 😉

#

Dependencies

At some point you may want to pull in third-party libraries. Without npm and a bundler this is still possible.

Unpkg

To use libraries without a bundler they need to be prebuilt in either ESM or UMD format. These libraries can be obtained from unpkg.com:

  1. Browse to unpkg.com/[library]/ (trailing slash matters), for example unpkg.com/microlight/
  2. Look for and download the library js file, which may be in a subfolder, like dist, esm or umd
  3. Place the library file in the lib/ folder

Alternatively, the library may be loaded directly from CDN.

UMD

The UMD module format is an older format for libraries loaded from script tag, and it is the most widely supported, especially among older libraries. It can be recognized by having typeof define === 'function' && define.amd somewhere in the library JS.

To include it in your project:

  1. Include it in a script tag: <script src="lib/microlight.js"></script>
  2. Obtain it off the window: const { microlight } = window;

ESM

The ESM module format (also known as JavaScript modules) is the format specified by the ECMAScript standard, and newer or well-behaved libraries will typically provide an ESM build. It can be recognized by the use of the export keyword.

To include it in your project:

imports.js

To neatly organize libraries and separate them from the rest of the codebase, they can be loaded and exported from an imports.js file. For example, here is a page that uses a UMD build of Day.js and an ESM build of web-vitals:

The text is rendered by the <x-metrics> component:

In the /lib folder we find these files:

Digging deeper into this last file we see how it bundles loading of third-party dependencies:

It imports the ESM library directly, but it pulls the UMD libraries off the Window object. These are loaded in the HTML.

Here is the combined example:

Regrettably not all libraries have a UMD or ESM build, but more and more do.

Import Maps

An alternative to the imports.js approach are import maps. These define a unique mapping between importable module name and corresponding library file in a special script tag in the HTML head. This allows a more traditional module-based import syntax in the rest of the codebase.

The previous example adapted to use import maps:

Some things to take into account when using import maps:

#

Browser support

Vanilla web sites are supported in all modern browsers. But what does that mean?

To keep up with new web standards, keep an eye on these projects:

#

Deploying

Any provider that can host static websites can be used for deployment.

An example using GitHub Pages:

  1. Upload the project as a repository on GitHub
  2. Go to Settings, Pages
  3. Source: GitHub Actions
  4. Static Website, Configure
  5. Scroll down to path, and change it to ./public
  6. Commit changes...
  7. Go to the Actions page for the repository, wait for the site to deploy
#

Testing

Popular testing frameworks are all designed to run in build pipelines. However, a plain vanilla web site has no build. To test web components an old-fashioned approach can be used: testing in the browser using the Mocha framework.

For example, these are the live unit tests for the <x-tab-panel> component used to present tabbed source code panels on this site:

And for ultimate coding inception, here is that tabpanel component showing the testing source code:

Some working notes on this approach:

#

Example

This website is the example. Check out the project on GitHub.


Up next

Learn how to build single-page applications using vanilla techniques.