selectflat is a dependency-free browser ESM reimplementation of Fil's Observable notebook @fil/selectflat.
It keeps the same core interaction model:
- hover or focus an option to preview it
- click to commit it
- read the current state from
form.value - listen to
inputfor live previews andchangefor committed updates
From npm:
npm install selectflatThen import it in your app:
import { selectFlat } from "selectflat";For local development in this repository, you can import the file directly:
import { selectFlat } from "./selectflat.js";<div id="app"></div>
<script type="module">
import { selectFlat } from "selectflat";
const control = selectFlat({
options: ["A", "B", "C", "D"],
value: "B",
output: true,
description: "your choice"
});
control.addEventListener("input", () => {
console.log("preview or current value", control.value);
});
document.querySelector("#app").append(control);
</script>You can set or reset the value from code:
import { selectFlat } from "selectflat";
const control = selectFlat({
options: ["A", "B", "C", "D"],
value: "C"
});
control.setValue("A");
control.resetValue();Setting control.value = "A" also updates the component and dispatches input and change.
import { selectFlat } from "selectflat";
const control = selectFlat({
multiple: true,
value: ["warm", "bright"],
options: [
{ label: "warm", value: "warm" },
{ label: "cool", value: "cool" },
{ label: "bright", value: "bright" },
{ label: "muted", value: "muted" }
]
});
console.log(control.value); // ["warm", "bright"]When multiple: true, hovering an option previews that option together with the values that are already selected.
You can customize the square size and strip layout with the layout option:
import { selectFlat } from "selectflat";
const control = selectFlat({
value: "D",
layout: {
size: "1.4rem",
gap: "0.25rem",
direction: "grid",
wrap: "wrap"
},
options: ["A", "B", "C", "D", "E", "F"]
});direction: "grid" wraps cells to the next row when the available width runs out.
By default, labels are hidden in the cells. You can reveal them on hover, keep them always visible, let the cell grow to fit the text, or clip the text inside a fixed cell:
import { selectFlat } from "selectflat";
const control = selectFlat({
text: {
visibility: "hover",
width: "content",
overflow: "clip"
},
options: ["Alpha", "Beta", "Gamma"]
});visibility accepts hidden, hover, and always.
width accepts fixed or content.
overflow accepts clip or ellipsis.
Options can render an image instead of text. Pass either an image URL, a data URL or base64 string, or an already loaded image element from the page:
import { selectFlat } from "selectflat";
const control = selectFlat({
output: true,
value: "fr",
options: [
{
value: "fr",
label: "France",
image: "https://raw.githubusercontent.com/lipis/flag-icons/main/flags/4x3/fr.svg",
fit: "contain"
},
{
value: "us",
label: "United States",
image: document.querySelector("#us-flag"),
fit: "contain"
}
]
});fit accepts CSS-like image fitting modes. contain keeps the whole image visible, cover
fills the square and may crop, and fill, none, and scale-down are also supported.
rescale is treated as contain, and crop or crope are treated as cover.
On touch devices:
- tap once to preview a value
- tap the same cell again to commit it
- drag across cells to preview continuously and commit on release
In multiple mode, drag gestures act like painting:
- start on an unselected cell to add cells across the drag path
- start on a selected cell to remove cells across the drag path
- forced and disabled cells are skipped
selectFlat(config) or selectFlat(optionsArray)
options: array of primitive values or option objectsvalue: initial value, or an array whenmultiple: truetitle: optional heading shown above the controldescription: optional text shown next to the outputoutput: whentrue, show the current label next to the descriptionmultiple: whentrue, allow toggling several values and return an arraylayout: optional layout object controlling option size and arrangementtext: optional text display settings for option labelssubmit: optional submit button label, ortruefor a defaultApplybutton
layout supports:
size: square width and height, as a CSS length or number of pixelsgap: spacing between cells, as a CSS length or number of pixelsdirection:row,row-reverse,column,column-reverse, orgridwrap:true/false, orwrap,nowrap,wrap-reverse
text supports:
visibility:hidden,hover, oralwayswidth:fixedorcontentoverflow:cliporellipsis
Option objects support:
value: underlying value returned fromform.valuelabel: label text used for tooltips, output text, and optional in-cell renderingdisabled: disable selection for that optionforced: whenmultiple: true, keep this option selected and do not allow it to be toggled offimage: optional image URL, data URL, base64 string, or already loaded image elementfit: image fit mode for this option:contain,cover,fill,none, orscale-down
selectFlat() returns a <form> element with:
form.value: current value, or an array whenmultiple: trueform.initialValue: the initial committed valueform.options: normalized option snapshotform.layout: normalized layout snapshotform.output: the<output>node used whenoutput: trueform.setValue(value, options?): programmatically commit a valueform.resetValue(options?): restore the initial committed value
Both setValue() and resetValue() dispatch input and change by default. Pass { dispatch: false } to update silently.
input: fires during hover and focus previews, and after committed updateschange: fires after committed click-based updates, programmatic updates, and resets
- This module is browser-only. It creates DOM nodes and expects
documentto exist. - The component injects scoped styles into the returned form, so no extra stylesheet is required.
- The control uses
touch-action: noneon the option strip so drag gestures work reliably on mobile. - This rewrite uses buttons instead of a styled native
<select>, but keeps the same flat strip interaction from the original notebook.
The demo page lives at examples/index.html. It includes examples for flags, emoji icons, multiple selection, layout control, and programmatic updates.
To run it locally:
python3 -m http.serverThen open http://localhost:8000/examples/.
This package is configured as a plain ESM npm module. A typical publish flow is:
npm test
npm publishIf selectflat is already taken on npm, update the name field in package.json before publishing.