How to use Ionic in a single HTML file

Ionic Framework works from a plain HTML file: one module script and one stylesheet from a CDN, then tags such as ion-button. No install and no build step.

"Ionic" here means Ionic Framework, an open source UI toolkit of ready-made components: buttons, lists, toggles, toolbars. It is not a build tool. In one HTML file you load it with a script tag and a stylesheet tag, then write tags like <ion-button>.

Try it first. Type a name, then press the button.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ionic in one HTML file</title>
<!-- Ionic web components + stylesheet, pinned to one version -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/css/ionic.bundle.css">
</head>
<body>
<ion-app>
  <ion-header>
    <ion-toolbar color="primary">
      <ion-title>Hello Ionic</ion-title>
    </ion-toolbar>
  </ion-header>
  <ion-content class="ion-padding">
    <ion-input id="name" label="Your name" label-placement="floating" fill="outline" placeholder="Type here"></ion-input>
    <p id="out">Nothing typed yet.</p>
    <ion-button id="greet" expand="block">Say hello</ion-button>
    <ion-toast id="toast" trigger="greet" message="Hello from a single HTML file" duration="2000"></ion-toast>
  </ion-content>
</ion-app>

<script>
  const input = document.getElementById('name');
  const out = document.getElementById('out');
  // ionInput fires on every keystroke; the text is in event.detail.value
  input.addEventListener('ionInput', (e) => {
    out.textContent = e.detail.value ? 'Hi, ' + e.detail.value + '!' : 'Nothing typed yet.';
  });
</script>
</body>
</html>
A toolbar, an input and a button, loaded from a CDN. The toast opens from the button.

Ionic's components are web components, so the browser treats ion-button and ion-input as ordinary tags once the script has registered them. Your own JavaScript can talk to them with normal addEventListener calls.

The two tags that load Ionic

Both tags go inside <head>. Ionic's documentation recommends jsDelivr for this.

<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/css/ionic.bundle.css">

The script defines the components. The stylesheet is the CSS bundle with all of Ionic's global styles.

The address above has @9.0.6 in it on purpose. Ionic's own snippet has no version number.

On 2026-10-01 the unversioned address returned version 9.0.4 while the npm registry listed 9.0.6 as latest. A pinned address always returns the same files, so your page keeps looking the same.

In short:

  1. Put the script and stylesheet in the head, with a version.
  2. Start the file with the doctype and wrap the body in one <ion-app>.
  3. Add <ion-header> and <ion-content> inside it.
  4. Listen to Ionic's events for input.

How the page is laid out

An Ionic page is a few nested tags. ion-app wraps everything, and Ionic's documentation says there should be only one per project. ion-header holds toolbars at the top. ion-content is the scrolling area and fills the height left over.

The nesting of an Ionic page and what each layer draws.
The nesting of an Ionic page and what each layer draws.

Plain HTML still works next to the components. In the first example, the paragraph that says "Hi, Ada!" is an ordinary <p> inside ion-content. The class ion-padding on ion-content adds the standard inner spacing.

Add the viewport meta tag as well, so the page is sized for a phone screen.

Reading input: Ionic's own events

Ionic components fire their own events, named with an ion prefix. The new value is in event.detail.

Component Event Read this from event.detail
ion-input ionInput value, on every keystroke
ion-toggle ionChange checked
ion-checkbox ionChange checked
ion-segment ionChange value

Buttons need no special event. A normal click listener works, as in the second and third examples.

iOS look, Material look, and your colors

Every Ionic component has a mode: ios or md (Material Design). Each platform has a default, and a component can override it with the mode attribute. Colors come from CSS variables, and the color attribute picks one of nine named colors.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ionic modes and colors</title>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/css/ionic.bundle.css">
<style>
  body { font-family: system-ui, sans-serif; margin: 0; padding: 14px; background: #f4f5f7; }
  .row { display: flex; flex-wrap: wrap; gap: 14px; }
  .panel { flex: 1 1 200px; background: #fff; border-radius: 10px; padding: 12px; }
  .panel h4 { margin: 0 0 10px; font-size: 14px; }
  .panel ion-button { display: block; margin-bottom: 10px; }
  /* Change a theme color for every Ionic component on the page */
  :root.brand {
    --ion-color-primary: #0f766e;
    --ion-color-primary-rgb: 15, 118, 110;
    --ion-color-primary-contrast: #ffffff;
    --ion-color-primary-contrast-rgb: 255, 255, 255;
    --ion-color-primary-shade: #0d6860;
    --ion-color-primary-tint: #278480;
  }
  .bar { margin-top: 14px; }
</style>
</head>
<body>
<div class="row">
  <div class="panel">
    <h4>mode="md" (Material)</h4>
    <ion-button mode="md">Solid</ion-button>
    <ion-button mode="md" fill="outline">Outline</ion-button>
    <ion-toggle mode="md" checked>Toggle</ion-toggle>
  </div>
  <div class="panel">
    <h4>mode="ios"</h4>
    <ion-button mode="ios">Solid</ion-button>
    <ion-button mode="ios" fill="outline">Outline</ion-button>
    <ion-toggle mode="ios" checked>Toggle</ion-toggle>
  </div>
</div>
<div class="bar">
  <ion-button id="theme" color="medium">Switch primary to teal</ion-button>
  <ion-button color="primary">Primary button</ion-button>
</div>

<script>
  const root = document.documentElement;
  const btn = document.getElementById('theme');
  // The class on <html> turns on the --ion-color-primary values above
  btn.addEventListener('click', () => {
    root.classList.toggle('brand');
    btn.textContent = root.classList.contains('brand') ? 'Switch primary back' : 'Switch primary to teal';
  });
</script>
</body>
</html>
The same components in both modes. The lower button swaps the primary color through a class on the html element.

To change a color, set every related variable, not only the main one. Ionic's documentation says to set all of them when changing a color. The example above sets six for primary.

A finished example: a task list

The same pieces make a small app. This one filters with ion-segment, ticks tasks with ion-checkbox, and adds new rows from an ion-input. The data lives in a plain JavaScript array, and nothing is saved when the page closes.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ionic task list</title>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.6/css/ionic.bundle.css">
</head>
<body>
<ion-app>
  <ion-header>
    <ion-toolbar>
      <ion-title>Tasks</ion-title>
    </ion-toolbar>
    <ion-toolbar>
      <ion-segment id="filter" value="all">
        <ion-segment-button value="all"><ion-label>All</ion-label></ion-segment-button>
        <ion-segment-button value="open"><ion-label>Open</ion-label></ion-segment-button>
        <ion-segment-button value="done"><ion-label>Done</ion-label></ion-segment-button>
      </ion-segment>
    </ion-toolbar>
  </ion-header>
  <ion-content>
    <ion-list id="list"></ion-list>
    <div class="ion-padding">
      <ion-input id="newTask" label="New task" label-placement="floating" fill="outline"></ion-input>
      <ion-button id="add" expand="block" class="ion-margin-top">Add task</ion-button>
      <p id="count" style="font-family:system-ui,sans-serif;font-size:14px"></p>
    </div>
  </ion-content>
</ion-app>

<script>
  // Data lives in a plain array; the list is redrawn from it
  const tasks = [
    { text: 'Paste the page into NOS', done: true },
    { text: 'Create share link', done: false },
    { text: 'Send it to a friend', done: false },
  ];
  let filter = 'all';
  const list = document.getElementById('list');
  const input = document.getElementById('newTask');

  function render() {
    list.innerHTML = '';
    tasks.forEach((t) => {
      if (filter === 'open' && t.done) return;
      if (filter === 'done' && !t.done) return;
      const item = document.createElement('ion-item');
      const box = document.createElement('ion-checkbox');
      box.checked = t.done;
      box.textContent = t.text;  // the checkbox label is its text content
      // ionChange: event.detail.checked holds the new state
      box.addEventListener('ionChange', (e) => { t.done = e.detail.checked; updateCount(); });
      item.appendChild(box);
      list.appendChild(item);
    });
    updateCount();
  }
  function updateCount() {
    const open = tasks.filter((t) => !t.done).length;
    document.getElementById('count').textContent = open + ' open, ' + (tasks.length - open) + ' done';
  }

  document.getElementById('filter').addEventListener('ionChange', (e) => {
    filter = e.detail.value;
    render();
  });
  document.getElementById('add').addEventListener('click', () => {
    const text = (input.value || '').trim();
    if (!text) return;
    tasks.push({ text, done: false });
    input.value = '';
    render();
  });
  render();
</script>
</body>
</html>
Filter with the segment, tick a task, add a new one. The counter updates on every change.

The page redraws the list from the array each time something changes. That keeps the code short enough to read in one screen.

When it does not work

The first three rows were reproduced with version 9.0.6 in a desktop browser. The others follow Ionic's documentation and the version check above.

What you see Cause Fix
Plain text where components should be The script tag is missing, blocked, or has a typo in the address Check the module script in the head
A button stretched tall The file has no doctype, so the browser uses quirks mode Make <!doctype html> the first line
ion-padding does nothing, margins look raw The stylesheet link is missing Add the CSS bundle link
Color only partly changes Only one color variable was set Set all six variables for that color
Different look on iPhone and desktop Default mode differs per platform Set the mode attribute
You get a different version than you tested The address has no version Add a version such as @9.0.6

The first two rows are easy to see side by side.

Without the script, the tags are never turned into components.
Without the script, the tags are never turned into components.
Without a doctype line, the same button grows to the height of the window.
Without a doctype line, the same button grows to the height of the window.

An Ionic page is made to be touched, so a screenshot loses the point. A bare .html attachment may open as plain code on a phone. Opening an HTML file on a phone covers why.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the two CDN tags load and the people you send it to can use the inputs and toggles themselves. If you change the code, the same link shows the new version.

Other single-file starting points are in single HTML file apps.

Questions people ask

Do I need to install anything to use Ionic in HTML?

Not for the plain JavaScript route. Ionic's documentation shows a script tag and a stylesheet tag that load from a CDN, and says this makes all of the core components usable without installing a framework.

Is Ionic free to use?

Ionic's documentation describes the UI toolkit as free and open source, released under the MIT license. Check the license of anything else you add to the page separately.

Can I use Ionic with React, Vue or Angular instead?

Yes. Ionic's documentation lists Angular, React, Vue and plain JavaScript. This guide covers only the plain JavaScript route, which is the one a single HTML file can use.

Why do my buttons look different on an iPhone and on my computer?

Ionic picks a mode per platform: ios on iOS devices and md (Material Design) elsewhere. Set the mode attribute on a component, for example mode="md", to make it look the same everywhere.

Can I just double-click the file to open it?

Yes, while the device is online. The two tags fetch Ionic from the CDN when the page loads, and the examples in this guide worked when opened from a local file.

Keep reading