Skip to the page
Tactile UI

Start here

Installation

There is nothing to install. Each piece is plain files you copy into your project.

On this page
  1. 1. Sign in
  2. 2. Download a package
  3. 3. Check it and run the example
  4. 4. Use it in your page
  5. Blocks
  6. Installing the kit

1. Sign in

There is no key. You buy while signed in with Google or GitHub, and the Pass lives on that account: sign in the same way on any device and everything is open. Your account page downloads everything at once.

2. Download a package

Your account page downloads everything you own as one file, tactile-ui.zip: every object and block with a Pass, the kit/ folder with the Kit, both with the Founder Pass, and the free pieces with any account. On any piece's page, Code then Download package gets that piece alone. Every zip unzips to one folder named like the file. A package for the Flip-Dot looks like this:

flip-dot-0.1.0/
├─ README.md                start here: the steps below, in full
├─ flip-dot.html            the runnable example, at the settings you chose
├─ flip-dot/
│  ├─ flip-dot.mjs          the object
│  └─ flip-dot.css          its styles
├─ prompts/
│  ├─ flip-dot.reproduce.md
│  └─ flip-dot.remix.md
├─ frameworks/              the same example for React, Vue, Svelte, a custom element
├─ wrappers/                the few lines those import
├─ index.html               links every example in the folder
├─ manifest.json            every file with its SHA-256
├─ verify.mjs               checks every file against manifest.json
├─ LICENCE.md
└─ serve.mjs                a tiny local server

A block's package also contains the folder of the object it is built with. tactile-ui.zip also has start.html, a first page with an object, a block and, with the Kit, three kit parts, and its README.md walks through it and through a fresh React app.

3. Check it and run the example

Inside the unzipped folder, node verify.mjs checks every file against manifest.json and prints all N files match manifest.json, or names the file that does not.

ES modules need a real web server: a browser won't import them from file://. Inside the unzipped folder:

node serve.mjs

Then open the address it prints: it lists every example. Add the example's file name to go straight to it, for example http://127.0.0.1:PORT/flip-dot.html. Any static server works as well.

4. Use it in your page

Copy the object's folder into your project, link its stylesheet and construct it on an element. The options are exactly the ones the Code tab shows.

<link rel="stylesheet" href="/flip-dot/flip-dot.css">
<div id="sign"></div>
<script type="module">
  import { FlipDot } from '/flip-dot/flip-dot.mjs';

  const sign = new FlipDot(document.getElementById('sign'), {
    text: 'NOW BOARDING',
    disc: 'fluorescent',
  });

  sign.setText('PLATFORM 4');     // the object's own verbs
  sign.update({ disc: 'white' }); // any option, any time
  // sign.destroy();              // removes listeners, observers and the canvas
</script>

Every object follows the same shape: new Object(element, options), update(options), destroy(). Each one sizes itself to its element, pauses when it is off screen and sleeps when nothing is moving.

Blocks

A block exports mount(element, options, dependencies) and is handed its object's class, so the same files work wherever you put them:

import { FlipDot } from '/flip-dot/flip-dot.mjs';
import { mount } from '/departures-board/departures-board.mjs';

const board = mount(document.getElementById('board'), { /* the Code tab's options */ }, { FlipDot });
// board.update({...}); board.destroy();

Next: Using a framework.

Installing the kit

The complete kit folder, a first-paint finish script and a class on the root. No build step, no framework, no runtime dependencies.

1Copy the files

Your account page downloads tactile-ui.zip with the Kit or the Founder Pass; it unzips to a tactile-ui/ folder whose README.md starts with a working page that uses the kit. Copy the entire kit/ folder from it into your project root, preserving every relative path and font licence. The entry module imports finishes.mjs and parts/; themes and the brain are needed too. Copying only the three files shown on an individual part page is not a complete kit installation.

your-site/
  kit/
    kit.css          styles, tokens and @font-face
    kit.mjs          behaviour entry module
    finishes.mjs     finish registry and switching
    parts/           modules imported by kit.mjs
    themes/          Wood, Materials, Light, Printed and Screens sheets
    fonts/           WOFF2 faces, with OFL licences
    brain/           rules, inventory, skills and verify tool

2The fonts

The kit needs two typefaces, and they ship with it: nothing is fetched from a font service. Instrument Sans (regular, italic, medium, semibold, bold) sets everything, with keys and labels at medium and semibold; Geist Mono (regular, bold) appears only inside displays. Both are under the SIL Open Font Licence; keep the two -OFL.txt files with them. The Printed and Screens families (kit/themes/printed.css, kit/themes/screens.css) add a third, Departure Mono (regular, OFL too), and load it themselves, so only a page in paper, phosphor, e-paper or AMOLED fetches it.

kit/fonts/InstrumentSans-Regular.woff2
kit/fonts/InstrumentSans-Italic.woff2
kit/fonts/InstrumentSans-Medium.woff2
kit/fonts/InstrumentSans-Medium-Ext.woff2
kit/fonts/InstrumentSans-SemiBold.woff2
kit/fonts/InstrumentSans-SemiBold-Ext.woff2
kit/fonts/InstrumentSans-Bold.woff2
kit/fonts/GeistMono-Regular.woff2
kit/fonts/GeistMono-Bold.woff2
kit/fonts/DepartureMono-Regular.woff2   Printed and Screens only
kit/fonts/CourierPrime-Regular.woff2    the Writer template's sheet only (OFL)

If you serve the stylesheet from somewhere else, edit the url('./fonts/…') lines at the top of kit.css (the -Ext files carry Latin Extended and load only when a page needs them). To preload the body face:

<link rel="preload" href="/kit/fonts/InstrumentSans-Regular.woff2" as="font" type="font/woff2" crossorigin>

3The head

Put class="tui" on <html>, and this script first in the head. It picks graphite when the system is dark and aluminium otherwise, before the first paint, and honours a choice the visitor made earlier.

<html lang="en" class="tui">
<head>
  <meta charset="utf-8">
  <script>(function(){var d=document.documentElement,F={'machined':['aluminium','graphite'],'wood':['oak','walnut'],'materials':['porcelain','black'],'light':['daylight','night'],'printed':['paper','phosphor'],'screens':['e-paper','amoled']},f,m,t,k;try{f=localStorage.getItem('tui-finish');m=localStorage.getItem('tui-family')}catch(e){}for(k in F)if(F[k].indexOf(f)>=0){m=k;t=F[k].indexOf(f)?'dark':'light'}if(!F[m])m='machined';t=t||(matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');d.dataset.finish=F[m][t==='dark'?1:0];d.dataset.tone=t;var w;try{w=localStorage.getItem('tui-wood')}catch(e){}if(w==='trims'||w==='full'||w==='inverted')d.dataset.wood=w})()</script>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/kit/kit.css">
  <link rel="stylesheet" href="/kit/themes/wood.css"><link rel="stylesheet" href="/kit/themes/materials.css"><link rel="stylesheet" href="/kit/themes/light.css"><link rel="stylesheet" href="/kit/themes/printed.css"><link rel="stylesheet" href="/kit/themes/screens.css">
</head>

Keep all four theme links to use non-machined finishes. These URLs assume the site root; for a page served below a path, use ./kit/ instead of /kit/ and keep the kit next to that page.

4A first panel

Every part is a class on a native element. This settings panel is the markup below, live:

Recording Ready
<div class="tui-panel">
  <div class="tui-panel__head">
    <span class="tui-label">Recording</span>
    <span class="tui-status"><span class="tui-lamp" data-on></span>Ready</span>
  </div>
  <label class="tui-choice">Phantom power <input type="checkbox" class="tui-switch" checked></label>
  <label class="tui-field">
    <span class="tui-label">Take name</span>
    <input class="tui-input" value="Harbour survey">
  </label>
  <button class="tui-key tui-key--primary">Start recording</button>
</div>

Need the knob, tabs or toasts? Add one line at the end of the body: <script type="module" src="/kit/kit.mjs"></script>.

5Check it

Run the brain’s verify tool on the page. It reads the markup and reports anything that breaks a law: a colour literal, an accent hue, two contrast keys on one surface, an unlabelled control, a display ghost of the wrong width, a missing finish script.

node kit/brain/tools/verify.mjs index.html

Then look at it in every finish and at 390px wide. Using it with Claude and Cursor goes further.

Use HTTP rather than opening an HTML file directly: browser modules do not work reliably under file://. The part's Copy prompt includes a complete index.html and dependency-free serve.mjs for a fresh project. It requires Node.js 18 or newer. Run node serve.mjs from the project root, open http://127.0.0.1:5173/, and stop it with Ctrl+C.