npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dodlhuat/basix

v1.5.5

Published

Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.

Readme

Basix 1.5.5

Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.

A demo can be found here: http://www.andibauer.at/basix/


Migration Guide

1.5.4 → 1.5.5

No breaking changes.

Other changes

  • Grid — new CSS Grid-based layout system, .grid/.cell, alongside the existing flexbox .row/.column. Auto-responsive by default (reflows without a breakpoint), or use .cols-1.cols-12 for a fixed number of tracks with .span-2.span-12/.span-full on cells. See CSS Grid Layout below.
  • Tokens — new --spacing, --border-radius, and --radius-xs/-sm/-md/-lg/-panel/-xl/-full custom properties on :root, mirroring the SCSS $spacing/$border-radius/$radius-* scale. Previously docs/foundation/colors.html referenced several --radius-* tokens that didn't actually exist; they're now real.
  • All SCSS variables in parameters.scss now carry !default, so they can be configured from a consuming stylesheet via @use "@dodlhuat/basix/css/style" with (...) without forking the file — this didn't compile before.
  • New Customizing docs page (docs/foundation/customizing.html) — full reference for every semantic and component-scoped token, which selector each one must be overridden on, and the dark-mode override pitfall. See Customizing below.
  • Fixed several docs pages that referenced custom properties that don't exist (typos like --secondary-bg for --secondary-background, --surface/--border for --secondary-background/--divider, --accent-tint for --accent-color-tint, --border-color for --divider, --primary-background for --background) — those elements were silently rendering without their intended background/border.

1.5.3 → 1.5.4

No breaking changes.

  • DatePicker — new min/max options to restrict the selectable date range. Either, both, or neither can be set. Individual days outside the range are disabled (greyed out, unclickable); months/years fully outside the range are disabled in the month/year picker views, and the prev/next navigation buttons disable themselves once navigating would land entirely outside the range.

1.5.2 → 1.5.3

Chart gained keyboard/screen-reader support and several new capabilities, following on from the 1.5.2 accessibility pass. No breaking changes.

  • Keyboard & screen reader access — every point, bar, and slice is now reachable with Tab and activatable with Enter/Space (fires onPointClick), with a visible focus indicator and an aria-label describing the value. The chart <svg> gets an accessible name via <title> (built from title or the series/segment names).
  • Pointer events replace mouse events — hover/tooltip handling now uses pointerenter/pointermove/pointerleave instead of the mouse-only equivalents, for better pen/touch behaviour.
  • Negative values fixedcolumn and bar bars used to clip negative values to 0. They now hang from a zero baseline, and yMin/yMax auto-detect the correct range when the data goes negative (only affects charts with negative values and no explicit yMin).
  • Clickable legend — legend entries are real <button>s that toggle their series (or pie slice) on/off, with a dimmed/struck-through state. Toggling all series off shows the empty state.
  • stacked — new option for column/bar to stack series instead of grouping them side-by-side.
  • innerRadius — new option for pie to render a donut, with a center label showing the total (or the hovered/focused slice).
  • showDataLabels — new option to print the value directly on each point/bar/slice.
  • Empty state — a chart with no data (or every series hidden via the legend) now shows an icon + message instead of a blank box. Configurable via the new iconBasePath and emptyMessage options.

SidebarNav now scrolls the active nav item into view on init, via a new activeSelector option (default '.is-active') — useful for a long nav list where the current page's link would otherwise be scrolled out of sight below the fold.

1.5.1 → 1.5.2

An accessibility and contrast pass across the whole framework, driven by an audit that measured actual WCAG contrast ratios rather than eyeballing them. Mostly bug fixes; a few components gained real functionality they were missing.

Breaking changes

Global box-sizing: border-box

reset.scss now sets box-sizing: border-box on *, *::before, *::after. Most components already declared this themselves, so the practical effect is limited — but if your own custom CSS sizes an element with both padding/border and an explicit width while relying on the browser default (content-box), it will now render slightly smaller. .styled-checkbox's checkmark position was tuned for the old sizing and has been corrected; check any custom pixel-tuned absolute positioning of your own the same way.

Other changes

  • Contrast fixes — several color tokens were validated as fills (which only need 3:1) but were also being used directly as text (which needs 4.5:1), and failed once actually measured — worst case 2.12:1. Fixed across DatePicker, Table pagination, Tree, Tooltip, form validation hints, Badge/Chip/Alert variants, ChatBubbles (message text, timestamps, avatar initials), and body-link hover/focus color in both themes.
  • New tokens in properties.scss: --on-accent (text/icons on an --accent-color fill), --link-color (body link color — --accent-color itself falls just under 4.5:1 as text in dark theme), --warning-text/--success-text/--error-text (text-safe variants of the status colors, which are calibrated as fills).
  • Removed the unused --accent-text token (dead code, not referenced anywhere).
  • Accordion — the toggle input is no longer display: none, which silently removed it from the tab order in every major browser. It's visually hidden instead, so the accordion is keyboard-operable again.
  • Switch — gained a focus-visible ring; it previously had no focus state at all, unlike Checkbox/RadioButton right next to it.
  • Dropdown — gained real keyboard navigation (arrow keys, Enter, Escape, submenu open/close) and basic ARIA (role="menu"/"menuitem", aria-expanded, aria-activedescendant). Previously mouse/touch only.
  • Popover — gained focus management for click-triggered popovers: focus moves into the panel on open, Tab is contained within it, and focus returns to the trigger on close. Hover-triggered popovers are unaffected (focus is never stolen from what you're doing on hover).
  • New breakpoint tokens in parameters.scss: $bp-sm (480px), $bp-md (768px), $bp-lg (1024px), replacing eight ad hoc pixel values used inconsistently across components. A few components' mobile-layout cutover shifted slightly wider as part of consolidating onto these (never narrower, so nothing gets more cramped than before).
  • FlyoutMenu — rebuilt on design tokens with proper dark-theme support; previously used a fixed dark palette regardless of [data-theme]. Also removed an unscoped global reset (* { box-sizing; margin; padding } and a body override) it was shipping, and some unused CSS (.site-header/.main-content/.logo) left over from an earlier mockup.
  • Touch targets — carousel indicator dots and the timespan-picker drag handle now have an invisible hit area reaching the WCAG 2.2 minimum (24px, 44px on touch viewports); the visible dot/handle size is unchanged.
  • ChatBubbles — the incoming bubble's background is now a subtle accent tint instead of plain --secondary-background, which is also the framework's standard card background (.card, .docs-demo) — the bubble was invisible against any card-like container it was placed in.
  • VirtualDropdown — new selectedLabel?: (count: number) => string option, called whenever the selection changes in multiSelect mode. Replaces the hardcoded English "N items selected" trigger summary for non-English UIs; the component doesn't pluralise for you, so return whatever's grammatically correct for count yourself (a ternary for languages with two plural forms, or Intl.PluralRules for languages with more). See docs/components/virtual-dropdown.html for examples.

1.5.0 → 1.5.1

No breaking changes.

  • Button — new .is-success state, meant to be toggled on after .is-loading once an async action resolves. Morphs the spinner into a checkmark; colored variants (button-primary/-success/-warning/-error, light and dark) are covered the same way .is-loading already was.
  • Icons — new .icon-toggle class for clickable icons (favorite, bookmark, star): press feedback via scale, plus a short pop and accent tint when .is-active is toggled on.
  • parameters.scss — new shared motion tokens: $duration-fast/$duration-base/$duration-slow and $ease-standard/$ease-spring/$ease-out-soft.
  • mixins.scss — new icon-rotate-transition and icon-pop mixins; the existing select-dropdown chevron now uses icon-rotate-transition internally (no visual change).
  • .card-hover — gained an :active press state (shadow settles, slight scale-back) as click feedback, in addition to the existing hover lift.
  • Accessibility — the form validation shake/hint-fade (.form-group.error/.success) and .card-hover's lift/press transform now respect prefers-reduced-motion; users who request reduced motion see the same end state without the animated transition.

1.4.3 → 1.5.0

Breaking changes

Font icons removed — SVG sprite is now the only icon system

The .icon base class, all .icon-* glyph classes, and the bundled MaterialSymbolsOutlined.woff2 font have been removed from icons.scss and fonts/. Every component that rendered a font-icon glyph now renders an inline <svg><use href="svg-icons/icons.svg#name"/></svg> instead. If your own markup used the font classes directly, replace them:

<!-- Before (1.4.3) -->
<span class="icon icon-close"></span>

<!-- After (1.5.0) -->
<svg class="icon-svg"><use href="svg-icons/icons.svg#close"/></svg>

A handful of glyph names used by the font don't exist under the same name in the sprite; use the sprite's equivalents:

| Font glyph | SVG sprite equivalent | |---|---| | icon-navigate_before / icon-navigate_next | chevron_left / chevron_right | | icon-expand_more | keyboard_arrow_down | | icon-light / icon-dark | light_mode / dark_mode |

Other changes

  • BottomSheet, Lightbox, GroupPicker, Modal, Toast, SidebarNav, Carousel, Select — gained an iconBasePath option (default 'svg-icons/') controlling where each component looks for the SVG sprite, matching the existing Calendar/Stepper convention.
  • PushMenu.init() now accepts an optional { iconBasePath } argument (previously took no arguments).
  • Lightbox.bind() now accepts an optional second iconBasePath argument, threaded through to every lightbox it opens.
  • New design tokens in parameters.scss: a 3-step tint scale ($tint-subtle/$tint-medium/$tint-strong) for state-color backgrounds, and an expanded, properly-ordered radius scale ($radius-xs/sm/md/lg/panel/xl). Several components' corner radii and background tints shifted slightly to align to these scales — no component's visual language changed, just consolidated toward consistent steps.
  • New shared mixins in mixins.scss: float-panel (floating dropdown/menu/popover shell), hide-scrollbar, tinted-tag (badge/chip tint), muted-icon-color (close-button hover).
  • Bug fixDatePicker's day-of-week headers and clock labels referenced an undefined --text-muted custom property (silently falling back to inherited text color). Fixed to use the existing --secondary-text token.
  • CI — added a GitHub Actions workflow (lint, typecheck, build, and a check that committed js//css build output matches source) and sass is now a real devDependency instead of relying on a global install.

1.4.2 → 1.4.3

No breaking changes.

  • DatePicker — the timePicker: true hour/minute picker is now an Android-style analog clock instead of up/down spinners. Tap or drag to pick the hour, then the minute, on a 24-hour dual-ring face (outer ring = even hours, inner ring = odd hours). selectedHours/selectedMinutes and all existing options are unchanged.
  • DatePicker — clock interactions (hour/minute selection, AM/PM-less 24h switching) now only re-render the time picker instead of rebuilding the whole calendar, avoiding unnecessary DOM churn.
  • Internal — the remaining per-component AbortController boilerplate (introduced in 1.4.1) has been consolidated into a small shared ListenerGroup utility (src/listeners.ts) used by every component's event lifecycle.
  • CSS bug fix — several component hover states (DatePicker's Set button and nav buttons, CodeViewer's copy button, FlyoutMenu's close button) were silently overridden by the global button reset due to a CSS specificity collision, so the intended hover color/background never rendered. Fixed by scoping each rule against the global reset.
  • CSS cleanup — consolidated duplicated box-shadow glow/focus-ring declarations across Calendar, Editor, Stepper, RangeSlider, TimePicker, form fields, and VirtualDropdown into shared mixins (field-shadow-rest, field-shadow-hover, field-shadow-focus) in mixins.scss. No visual change.
  • Cleanup — removed unused legacy files (form-builder.js, guitar-chords.js/.css, lazy-loader.js, request.js) and a stale, pre-integration copy of calendar.css, plus dead .datepicker-select CSS.
  • Type fixes — resolved two pre-existing TypeScript errors in Tabs and VirtualDropdown (keydown listeners typed against Element instead of HTMLElement).

1.4.1 → 1.4.2

No breaking changes.

  • RangeSlider — added a two-thumb start/end variant, RangeSliderRange. Wrap two <input type="range"> elements in .range-slider.range-slider--range and initialise with new RangeSliderRange(container). The thumbs cannot cross each other; read the current values with values(), which returns [start, end].

1.4.0 → 1.4.1

No breaking changes. Internal code quality pass across all TypeScript source files:

  • All components — event listeners migrated to AbortController pattern; arrow class fields replaced with regular methods. destroy() now consistently calls abortController.abort() instead of individual removeEventListener calls.
  • Various — optional chaining (?.) for callbacks, template literals over string concatenation, querySelector<T> generics over as casts, redundant type annotations removed.

1.3.5 → 1.4.0

Breaking changes

TimeSpanPicker — option toString renamed to toLabel

The toString option shadowed Object.prototype.toString and is now renamed to toLabel. Update any call sites that pass this option:

// Before (1.3.5)
new TimeSpanPicker('container', { toString: 'Bis' });

// After (1.4.0)
new TimeSpanPicker('container', { toLabel: 'Bis' });

Bug fixes

  • MasonryGallery — the reload option was previously hard-coded to 2 and ignored. It now respects the value you pass. The default remains 2, so existing usage without the option is unaffected.
  • Tabsdestroy() no longer operates on detached DOM nodes. Event listeners are now cleaned up via AbortController.
  • gallery — image-load errors no longer cause an unhandled promise rejection; they are logged to console.error instead.

Benefits

  • lightweight
  • customizable
  • no dependencies, completely vanilla javascript (or css only)

Usage

Take a look at style.scss for a glimpse on a full import. reset, parameters, colors & defaults are mandatory, anything else can be added as needed.

Include the stylesheet and import individual components as ES modules. There is no bundled entry point — only the components you use are loaded.

<link rel="stylesheet" href="css/style.css" type="text/css">
<script type="module">
    import { Chart }   from './js/chart.js';
    import { Modal }   from './js/modal.js';
    import { Stepper } from './js/stepper.js';
    // … add only what you need
</script>

When installed via npm, import from the package:

import { Chart }   from '@dodlhuat/basix/js/chart.js';
import { Modal }   from '@dodlhuat/basix/js/modal.js';

Available Components

Layout

Grid System

The Grid component provides a flexbox-based layout system. Use .row with .column children. Columns can use .grow-2 through .grow-6 for proportional sizing. Responsive — stacks on mobile.

<div class="row">
    <div class="column">Column 1</div>
    <div class="column grow-2">Column 2 (2x)</div>
    <div class="column">Column 3</div>
</div>

CSS Grid Layout

The Grid component also offers a CSS Grid-based layout, .grid with .cell children, as an alternative to .row/.column. By default it reflows automatically without a breakpoint; use .cols-1 through .cols-12 for a fixed number of tracks, and .span-2 through .span-12 (or .span-full) on a .cell for an explicit column layout. Responsive — stacks to a single column on mobile. Rule of thumb: use .row/.column when content should size the layout (toolbars, form rows), and .grid/.cell when the layout should size the content (card grids, anything that needs to align across multiple rows) — both are independent, neither is deprecated in favor of the other.

<div class="grid cols-12">
    <div class="cell span-8">Main</div>
    <div class="cell span-4">Aside</div>
</div>

Typography

The typography system is built around the Outfit variable font, providing a modern, readable base for all text. Headings follow a clear scale with bold weight and tighter line spacing for strong visual hierarchy. Utility classes enable simple text alignment. Monospace fonts are reserved for code.

Cards

The Card component is a CSS-only component that creates visually contained content sections with optional header and footer. Use .card on rows or columns to wrap them into a card.

<div class="row card">
    <div class="column">Card content</div>
</div>

Icons

Basix ships the full Google Material Icon set (~6,400+ icons) as a single svg-icons/icons.svg sprite. Reference any icon by name with <use>, and style size/color with the .icon-svg class — it inherits currentColor, so the icon follows the surrounding text color automatically.

<svg class="icon-svg"><use href="svg-icons/icons.svg#home"/></svg>

Components that render icons from JS (Modal, Toast, BottomSheet, Lightbox, Carousel, GroupPicker, SidebarNav, Calendar, Stepper, PushMenu, Select, ContextMenu) accept an iconBasePath option (default 'svg-icons/') so you can point them at the sprite's location relative to your page:

new Modal({ content: '…', iconBasePath: '../svg-icons/' });

A couple of purely-decorative icons that need to render with zero markup (the Breadcrumb separator, the native <select> dropdown arrow) use mask-image against small standalone SVGs (svg-icons/chevron_right.svg, svg-icons/keyboard_arrow_down.svg) instead of the sprite, since sprite symbols are only instantiable via <use>.


Theming & Customization

Basix has two independent customization surfaces: CSS custom properties, overridable at runtime with no build step, and SCSS variables, configured at compile time via @use … with (…).

Custom properties come in two layers — semantic tokens on :root (--accent-color, --divider, --error, …, defined in css/properties.scss) and component-scoped tokens declared on the component's own class rather than :root (--cal-accent on .cal, --chart-color-1 on .chart, --menu-width on .flyout-menu, …), which default to a semantic token but can diverge per component. Because component tokens are scoped to their component's class, :root { --cal-accent: red } has no effect — it must be .cal { --cal-accent: red }, since a directly-matched class selector always wins over an inherited :root value.

/* your-theme.css — loaded AFTER css/style.css */
:root {
    --accent-color: #6C47FF;
    --radius-md: 0.5rem;
}

Basix ships its palette in three blocks (:root, [data-theme="dark"], and a prefers-color-scheme fallback) — an override placed only on :root reverts the moment dark mode activates, so re-theme all three when touching a colour token.

SCSS variables in parameters.scss ($spacing, $border-radius, $bp-sm/$bp-md/$bp-lg, $duration-*, …) all carry !default and can be configured without forking the file:

@use "@dodlhuat/basix/css/style" with (
    $spacing: 1.25rem,
    $border-radius: 0.5rem
);

For the full token reference — every semantic and component token, its default, and which selector it must be overridden on — see the Customizing docs page.


Forms

The Form styles provide consistent styling for inputs, textareas, and native elements.

Text Input

<label for="my-input">Text Input</label>
<input type="text" id="my-input" />

Textarea

<label for="my-textarea">Text Area</label>
<textarea id="my-textarea"></textarea>

Checkbox

The Checkbox component provides custom-styled checkboxes.

<input class="styled-checkbox" id="checkbox-1" type="checkbox" value="1" />
<label for="checkbox-1">Checkbox</label>

Radio Buttons

The Radio Button component provides custom-styled radio inputs.

<label class="radio-button-container">One
    <input type="radio" checked="checked" name="radio" />
    <span class="checkmark"></span>
</label>

Switch

The Switch component creates styled toggle switches based on checkboxes.

<div class="switch">
    <input type="checkbox" id="switch" />
    <label for="switch">Toggle</label>
</div>

Range Slider

The Range Slider component creates a styled slider with a CSS custom property --range-fill that tracks the current value for fill styling. Use the JS class to initialise fill tracking automatically.

<div class="range-slider">
    <input type="range" min="1" max="100" value="50" />
</div>
// Initialise a single slider
new RangeSlider(document.querySelector('.range-slider input'));

// Or initialise all sliders on the page at once
RangeSlider.initAll();

Range variant — a two-thumb slider for selecting a start and end value. Wrap two range inputs in .range-slider.range-slider--range and initialise with RangeSliderRange. The thumbs cannot cross each other.

<div class="range-slider range-slider--range">
    <input type="range" min="0" max="100" value="25" aria-label="Minimum value" />
    <input type="range" min="0" max="100" value="75" aria-label="Maximum value" />
</div>
const slider = new RangeSliderRange(document.querySelector('.range-slider--range'));

// Read the current [start, end] values
const [start, end] = slider.values();

// Or initialise all range sliders on the page at once
RangeSliderRange.initAll();

Navigation

Push Menu

The PushMenu component creates a sidebar navigation that "pushes" the main content when opened. Built on a CSS checkbox toggle — no JS required to open/close. Nested <ul> items are automatically extracted into sliding sub-panels. It's a static class: call PushMenu.init() once after the required elements exist in the DOM.

<!-- Header: hamburger toggle -->
<div class="main-header">
    <div class="navigation-controls">
        <input type="checkbox" id="menu-navigation" class="navigation"/>
        <label for="menu-navigation"><svg class="icon-svg"><use href="svg-icons/icons.svg#menu"/></svg></label>
    </div>
</div>

<!-- Sidebar nav -->
<nav class="push-menu">
    <ul>
        <li><a href="#">Dashboard</a></li>
        <li>
            <a>Settings</a>
            <ul>
                <li><a href="#">Account</a></li>
                <li><a href="#">Privacy</a></li>
            </ul>
        </li>
        <li><a href="#">Logout</a></li>
    </ul>
</nav>
<div class="push-menu-backdrop"></div>

<!-- Main content area -->
<div class="content push-content"><!-- page content --></div>
import { PushMenu } from '@dodlhuat/basix/js/push-menu.js';

PushMenu.init();          // call once after DOM is ready

PushMenu.open();
PushMenu.close();
PushMenu.isOpen();        // boolean
PushMenu.refresh();       // re-query DOM elements after dynamic changes
PushMenu.destroy();       // remove listeners and reset state

| Option | Type | Default | Description | |---|---|---|---| | iconBasePath | string | 'svg-icons/' | Base path to the SVG icon sprite, used by sub-panel back/chevron icons |

Required selectors: .navigation (the checkbox), .push-menu (the <nav>), .push-content (the pushed content wrapper), .main-header (the top bar that moves with the content), .push-menu-backdrop (closes the menu on click).

Flyout Menu

The Flyout Menu component creates slide-in navigation menus with nested submenus. Supports left/right direction, header/footer and keyboard navigation (Escape to close).

<button id="my-trigger">Open menu</button>
<div class="flyout-overlay" id="flyoutOverlay"></div>
<div class="flyout-menu" id="flyoutMenu">
    <ul>
        <li>Section
            <ul>
                <li><a href="#">Item A</a></li>
                <li><a href="#">Item B</a></li>
            </ul>
        </li>
        <li><a href="#">Standalone link</a></li>
    </ul>
</div>
const flyout = new FlyoutMenu({
    triggerSelector: '.trigger-flyout-menu', // opens the menu
    menuSelector:    '#flyoutMenu',
    overlaySelector: '#flyoutOverlay',
    closeSelector:   '.close-menu',          // elements inside that close on click
    direction:       'right',                // 'right' | 'left'
    title:           'Navigation',
    enableHeader:    true,
    footerText:      '&copy; 2026 Brand Inc.',
    enableFooter:    true,
});

flyout.open();
flyout.close();
flyout.setDirection('left');
flyout.destroy();

| Option | Type | Default | Description | |---|---|---|---| | triggerSelector | string | '.menu-trigger' | CSS selector for the element(s) that open the menu | | menuSelector | string | '#flyoutMenu' | CSS selector for the flyout menu element | | overlaySelector | string | '#flyoutOverlay' | CSS selector for the backdrop overlay | | closeSelector | string | '.close-menu' | CSS selector for close button(s) inside the menu | | submenuToggleSelector | string | '.submenu-toggle' | CSS selector for nested submenu toggle elements | | direction | string | 'right' | Slide-in direction, either 'right' or 'left' | | title | string | 'Menu' | Shown in the header if enabled | | enableHeader | boolean | true | Shows the menu header | | footerText | string | '© 2025 Brand Inc.' | Shown in the footer if enabled | | enableFooter | boolean | true | Shows the menu footer |

Sidebar Nav

The SidebarNav component wraps an always-visible sidebar on desktop that collapses into a slide-in drawer on mobile — a backdrop, swipe-to-open/close gestures, and auto-scroll to the active nav item on init.

<div class="sidebar-layout">
    <aside class="sidebar-nav" id="my-sidebar"><!-- nav content --></aside>
    <div class="sidebar-backdrop"></div>
    <div class="sidebar-main">
        <button class="sidebar-toggle" aria-label="Open sidebar">☰</button>
        <!-- page content -->
    </div>
</div>
const nav = new SidebarNav('.sidebar-layout', {
    toggleSelector: '#my-toggle',
    breakpoint: 768,
});

nav.open();
nav.close();
nav.toggle();
nav.isOpen();   // boolean
nav.destroy();

| Option | Type | Default | Description | |---|---|---|---| | toggleSelector | string | '.sidebar-toggle' | CSS selector for the hamburger toggle button | | breakpoint | number | 768 | Viewport width (px) above which the sidebar is permanently visible and the drawer auto-closes | | swipeThreshold | number | 60 | Minimum horizontal swipe distance (px) to trigger open/close on touch | | swipeEdge | number | 20 | Width (px) of the left-edge zone that triggers open on swipe-right | | iconBasePath | string | 'svg-icons/' | Base path to the SVG icon sprite, used by the injected close button | | activeSelector | string | '.is-active' | Selector (searched within the sidebar) for the active nav item, scrolled into view on init if present |

Popover

The Popover component attaches a floating content panel to any trigger element. Supports click and hover trigger modes, four placement directions, optional arrow, and stacks correctly when multiple popovers exist.

| Option | Type | Default | Description | |---|---|---|---| | content | string | — | HTML content rendered inside the popover | | placement | string | 'top' | Preferred placement: 'top', 'bottom', 'left', 'right', or 'auto' | | align | string | 'center' | Alignment along the axis: 'start', 'center', 'end' | | offset | number | 8 | Distance in px between the trigger and the popover | | arrow | boolean | true | Shows a directional arrow | | triggerMode | string | 'click' | 'click' or 'hover' | | closeOnOutsideClick | boolean | true | Closes when clicking outside | | closeOnEscape | boolean | true | Closes on Escape key | | className | string | — | Extra CSS class on the popover element | | onOpen | function | — | Callback fired when the popover opens | | onClose | function | — | Callback fired when the popover closes |

const pop = new Popover('#my-trigger', {
    content: '<p>Popover content</p>',
    placement: 'bottom',
    triggerMode: 'click',
});

pop.open();
pop.close();
pop.toggle();
pop.destroy();

Dropdown Menu

The Dropdown Menu allows to create multi-level dropdown menus with nested submenus. The menu fires custom events CustomEvent<DropdownSelectDetail> that can be listened to in order to react to user selections.

<div class="dropdown-container" id="myDropdown">
    <button class="dropdown-trigger">Select Option</button>
    <ul class="dropdown-menu">
        <li><div class="dropdown-item">Profile</div></li>
        <li>
            <div class="dropdown-item">Settings</div>
            <ul>
                <li><div class="dropdown-item">Account</div></li>
            </ul>
        </li>
    </ul>
</div>

Feedback

Modal

The Modal component creates dialog overlays with header, content, and footer sections. Supports type variants (success, error, warning, info) and closes on Escape key or backdrop click.

| Option | Type | Default | Description | |---|---|---|---| | content | string | — | Content of the modal. Can be HTML or a simple string | | header | string | — | Header of the modal. Can be HTML or a simple string | | footer | string | — | Footer of the modal. Can be HTML or a simple string | | closeable | boolean | true | Shows a close button and enables Escape/backdrop-click dismissal | | type | ModalType | 'default' | 'default', 'success', 'error', 'warning', or 'info' | | iconBasePath | string | 'svg-icons/' | Base path to the SVG icon sprite, used by the close icon |

const modal = new Modal({
    header: 'Delete item?',
    content: 'This action cannot be undone.',
    footer: '<div class="buttons"><button>Cancel</button><button>Delete</button></div>',
    type: 'error',
});

modal.show();
modal.hide();
modal.updateContent('<p>New content</p>');
modal.isVisible();   // boolean
modal.destroy();

Toast

The Toast component shows brief notification messages.

| Parameter | Type | Description | |---|---|---| | content | string | The content of the toast | | header | string | The header of the toast | | markup | ToastType | Changes the color of the toast: default, success, warning, error | | closeable | boolean | Allows to close the toast before auto-closure time | | auto-closure | integer | Optional closure time in ms |

Bottom Sheet

The Bottom Sheet component slides up a panel from the bottom of the screen. On desktop it centers as a floating panel. Supports drag-to-dismiss, snap heights, header/footer slots, and an optional close button.

| Option | Type | Default | Description | |---|---|---|---| | content | string | — | HTML content rendered inside the sheet body | | header | string | — | Optional header text | | footer | string | — | Optional footer HTML (e.g. action buttons) | | closeable | boolean | true | Shows a close button and enables backdrop/Escape dismissal | | snapHeight | string | 'auto' | Height preset: 'auto', 'half' (50vh), or 'full' (100dvh) | | onClose | function | — | Callback fired after the dismiss animation completes |

const sheet = new BottomSheet({
    content: '<p>Sheet content here.</p>',
    header: 'Title',
    footer: '<div class="buttons"><button>Cancel</button><button>Confirm</button></div>',
    snapHeight: 'half',
    onClose: () => console.log('closed'),
});
sheet.show();
sheet.hide();
sheet.snapTo('full');

Lightbox

The Lightbox component opens images in a fullscreen overlay with an optional gallery mode. Supports keyboard navigation (← →, Escape), touch swipe, click-to-zoom, adjacent image preloading, focus trap, and a static bind() method for declarative HTML wiring.

| Option | Type | Default | Description | |---|---|---|---| | src | string | — | Image URL for single-image mode | | alt | string | — | Alt text for the image | | caption | string | — | Optional caption below the image | | images | LightboxImage[] | — | Array of { src, alt?, caption? } for gallery mode | | startIndex | number | 0 | Starting index when opening a gallery | | closeable | boolean | true | Shows × button and enables backdrop/Escape dismissal | | onOpen | function | — | Callback fired when the lightbox opens | | onClose | function | — | Callback fired after the close animation completes |

import { Lightbox } from '@dodlhuat/basix/js/lightbox.js';

// Single image
new Lightbox({ src: 'photo.jpg', alt: 'A landscape', caption: 'Taken at sunrise' }).show();

// Gallery
new Lightbox({
    images: [
        { src: 'photo1.jpg', alt: 'Photo 1', caption: 'Day one' },
        { src: 'photo2.jpg', alt: 'Photo 2' },
    ],
    startIndex: 0,
    onClose: () => console.log('closed'),
}).show();

// Declarative binding — groups elements by data-lightbox value into galleries
Lightbox.bind();
<!-- Declarative usage -->
<a href="full.jpg" data-lightbox="trip" data-lightbox-caption="Arrival day">
    <img src="thumb.jpg" alt="Arrival" />
</a>

Tooltip

The Tooltip component shows contextual information on hover.

<button class="tooltip-trigger" data-tooltip="This is a simple tooltip">Hover me</button>

Spinner / Loading

The Spinner component shows simple loading indicators. There are two variants: a simple spinner and a loading indicator with dots.

<div class="spinner"></div>
<div class="loading"></div>

Components

Alerts

The Alert component displays contextual feedback messages. Available variants: default, error, warning and success.

<div class="alert alert-error"><strong>Error: </strong> This is an error alert!</div>

Buttons

The Button component provides styled buttons with variants. Use the .button class on divs or simply the button element with color classes: button-primary, button-success, button-warning, button-error.

<button class="button-primary">Primary</button>

Chips

The Chips component displays small interactive elements like tags or filters. CSS only. Use listeners if you want them to be clickable or closeable.

<div class="chips">
    <div class="chip">Example Chip</div>
    <div class="chip clickable">Clickable Chip</div>
    <div class="chip closeable">
        Closeable Chip
        <button class="close"><svg class="icon-svg"><use href="svg-icons/icons.svg#close"/></svg></button>
    </div>
</div>

Badge

The Badge component displays compact status labels or counts inline. CSS only. Supports soft (tinted), solid, and outline variants, three sizes, and a dot-only indicator.

<!-- Soft (default) -->
<span class="badge badge-info">Info</span>
<span class="badge badge-success">Success</span>
<span class="badge badge-warning">Warning</span>
<span class="badge badge-error">Error</span>

<!-- Solid fill -->
<span class="badge badge-solid badge-info">Info</span>

<!-- Outline -->
<span class="badge badge-outline badge-error">Error</span>

<!-- Sizes -->
<span class="badge badge-sm badge-info">Small</span>
<span class="badge badge-lg badge-info">Large</span>

<!-- Dot indicator (no text) -->
<span class="badge badge-dot badge-success"></span>

Stepper

The Stepper component guides users through a multi-step process. Supports horizontal and vertical layouts, animated connector fills, active pulse, completed, and error states. Steps can optionally be made clickable for non-linear navigation.

<div class="stepper" id="my-stepper">
    <div class="stepper-step completed">
        <div class="stepper-indicator">
            <svg class="icon-svg"><use href="svg-icons/icons.svg#check"/></svg>
        </div>
        <div class="stepper-label">
            <span class="stepper-title">Account</span>
            <span class="stepper-desc">Your details</span>
        </div>
    </div>
    <div class="stepper-connector completed"></div>
    <div class="stepper-step active">
        <div class="stepper-indicator">2</div>
        <div class="stepper-label">
            <span class="stepper-title">Profile</span>
        </div>
    </div>
    <div class="stepper-connector"></div>
    <div class="stepper-step">
        <div class="stepper-indicator">3</div>
        <div class="stepper-label">
            <span class="stepper-title">Review</span>
        </div>
    </div>
</div>

Add .stepper-vertical to the container for a vertical layout.

const stepper = new Stepper('#my-stepper', {
    defaultStep: 0,   // initial active step (0-based)
    clickable: true,  // allow clicking completed steps to navigate back
    onChange: (current, previous) => console.log(current, previous),
});

stepper.next();
stepper.prev();
stepper.goTo(2);
stepper.setError(1);
stepper.clearError(1);
stepper.isFirst();   // boolean
stepper.isLast();    // boolean
stepper.getStep();   // current index
stepper.getStepCount();

| Option | Type | Default | Description | |---|---|---|---| | defaultStep | number | 0 | Index of the initially active step | | clickable | boolean | false | Adds .stepper-clickable and wires click-to-navigate on all steps | | onChange | function | — | Callback fired on step change: (current, previous) => void |

Accordion

The Accordion component creates collapsible content sections. Uses hidden radio/checkbox inputs with labels and is CSS only.

<div class="accordion">
    <div class="accordion-item">
        <input type="radio" name="accordion" id="acc1" class="accordion-input" checked />
        <label for="acc1" class="accordion-label">Section Title</label>
        <div class="accordion-content">
            <div class="accordion-body"><div><p>Content here.</p></div></div>
        </div>
    </div>
</div>

Tabs

The Tabs component creates accessible tabbed interfaces. Supports horizontal/vertical layouts, keyboard navigation (arrow keys, Home, End), and ARIA attributes.

| Option | Type | Default | Description | |---|---|---|---| | layout | string | 'horizontal' | 'horizontal' or 'vertical' | | defaultTab | number | 0 | Index of the initially active tab (0-based) | | menuPos | string | 'top' / 'left' | Tab bar position: 'top', 'bottom', 'left', 'right'. Defaults to 'top' for horizontal layout, 'left' for vertical. | | onChange | function | — | Callback fired on tab change: (index: number) => void |

| Method | Description | |---|---| | goToTab(index) | Programmatically activate a tab and focus it | | getCurrentTab() | Returns the index of the currently active tab | | getTabCount() | Returns the total number of tabs | | enableTab(index) | Re-enables a disabled tab | | disableTab(index) | Disables a tab; if it was active, moves focus to the next enabled tab | | destroy() | Removes event listeners and cleans up ARIA attributes |

Timeline

The Timeline component displays chronological events. CSS only.

<div class="timeline">
    <div class="timeline-item active">
        <div class="timeline-content">
            <span class="timeline-date">October 12, 2023</span>
            <h3 class="timeline-title">Event Title</h3>
            <p class="timeline-body">Event description.</p>
        </div>
    </div>
</div>

Progress Bar

The Progress Bar component displays task completion.

<div class="progress-bar">
    <div class="progress" style="height: 24px; width: 50%"></div>
</div>

Placeholder / Skeleton

The Placeholder component creates skeleton loading states. Use .placeholder with width classes .w-1 through .w-12 (12-column grid). Animates with a pulsing fade effect.

<span class="placeholder w-6"></span>

Advanced Components

Chart

The Chart component renders SVG-based charts with no external dependencies. Supports line, area, column, bar, and pie chart types (with optional stacking and a donut mode). Animates on first render and redraws on container resize. Every point/bar/slice is keyboard-reachable and screen-reader labelled, and the legend is clickable to toggle series/slices on and off.

const chart = new Chart('#chart-container', {
    type: 'line',
    title: 'Monthly Revenue',
    series: [
        {
            name: 'Product A',
            data: [
                { label: 'Jan', value: 120 },
                { label: 'Feb', value: 180 },
                { label: 'Mar', value: 150 },
            ],
        },
    ],
});

chart.update(newSeries);   // replace data and redraw
chart.setType('bar');      // switch chart type
chart.destroy();           // remove listeners and DOM

| Option | Type | Default | Description | |---|---|---|---| | type | ChartType | — | 'line', 'area', 'column', 'bar', or 'pie' | | series | ChartSeries[] | — | Array of { name, data, color? } objects | | title | string | — | Optional chart title | | subtitle | string | — | Optional subtitle below the title | | height | number | 280 | Inner chart height in px | | showLegend | boolean | true | Renders the series legend. Entries are clickable to toggle a series/slice | | showGrid | boolean | true | Renders background grid lines | | showDataLabels | boolean | false | Prints the value on each point/bar/slice | | animate | boolean | true | Animates on first render | | curve | string | 'smooth' | Line interpolation for line/area: 'smooth', 'linear', 'step' | | stacked | boolean | false | Stack series on top of each other. column/bar only | | innerRadius | number | 0 | Donut hole radius as a fraction of the pie radius (0–1). pie only | | yMin | number | auto | Fixed axis minimum (defaults to 0, or the lowest value × 1.1 if negative) | | yMax | number | auto | Fixed axis maximum (defaults to max value × 1.1) | | iconBasePath | string | 'svg-icons/' | Base path to the SVG sprite, used by the empty-state icon | | emptyMessage | string | 'No data to display' | Message shown when there is no data to render | | onPointClick | function | — | Callback (series, point, index) => void fired on activation (click, or Enter/Space while focused) |

Calendar

The Calendar component renders a full interactive calendar with month, week, and agenda views. Supports event display, keyboard navigation, and locale configuration.

const cal = new Calendar({
    container: '#my-calendar',
    view: 'month',
    events: [
        {
            id: '1',
            title: 'Team Meeting',
            start: new Date(2026, 3, 20, 10, 0),
            end:   new Date(2026, 3, 20, 11, 0),
            className: 'badge-success',
        },
    ],
    onEventClick: (event) => console.log(event),
    onDayClick:   (date)  => console.log(date),
    onChange:     (date, view) => console.log(date, view),
});

cal.next();
cal.prev();
cal.today();
cal.setView('week');
cal.addEvent({ id: '2', title: 'Lunch', start: new Date(), end: new Date() });
cal.removeEvent('2');
cal.setEvents(events);
cal.getEvents();
cal.destroy();

| Option | Type | Default | Description | |---|---|---|---| | container | HTMLElement | string | — | Target container element or CSS selector | | events | CalendarEvent[] | [] | Initial events | | view | string | 'month' | Initial view: 'month', 'week', or 'agenda' | | showOutsideDays | boolean | true | Show days from adjacent months in the month grid | | locale | object | — | Override locale strings (day names, month names, labels) | | onDayClick | function | — | Callback (date: Date) => void | | onEventClick | function | — | Callback (event: CalendarEvent) => void | | onChange | function | — | Callback (date: Date, view: CalendarView) => void | | className | string | — | Extra CSS class on the root element |

Context Menu

The Context Menu component shows a custom right-click menu on any target element. Supports icons, keyboard shortcuts, group labels, separators, submenus, destructive items, and disabled items. Automatically flips to avoid viewport overflow and animates in from the click origin.

new ContextMenu('.my-element', [
    { group: 'File' },
    { label: 'Open',   icon: 'folder_open', shortcut: '⌘O', action: (target) => {} },
    { label: 'Rename', icon: 'edit',        shortcut: 'F2', action: (target) => {} },
    'separator',
    {
        label: 'Share', icon: 'share',
        submenu: [
            { label: 'Copy link',    icon: 'link', action: (target) => {} },
            { label: 'Send by mail', icon: 'mail', action: (target) => {} },
        ]
    },
    'separator',
    { label: 'Delete', icon: 'delete', destructive: true, action: (target) => {} },
], { spritePath: 'svg-icons/icons.svg' });

The constructor accepts a CSS selector string, a single HTMLElement, or an array of HTMLElements as the first argument. The optional third argument is an options object.

Options

| Option | Type | Default | Description | |---|---|---|---| | spritePath | string | — | Path to the SVG sprite file (e.g. 'svg-icons/icons.svg'). Icons are only rendered when this is set. |

Item types

| Type | Shape | Description | |---|---|---| | Action item | ContextMenuItemDef | Regular clickable item | | Separator | 'separator' | Horizontal divider line | | Group label | { group: string } | Non-interactive section header |

ContextMenuItemDef

| Property | Type | Description | |---|---|---| | label | string | Display text | | icon | string | SVG sprite icon ID (e.g. 'delete'). Requires spritePath option. | | shortcut | string | Keyboard shortcut hint shown on the right (e.g. '⌘O') | | disabled | boolean | Renders item at reduced opacity, non-interactive | | destructive | boolean | Renders item in error/red color | | action | function | Callback (target: HTMLElement) => void — receives the right-clicked element | | submenu | ContextMenuInput[] | Nested items; renders a submenu on hover |

Keyboard navigation

| Key | Action | |---|---| | / | Move focus between items | | Enter | Activate focused item | | Escape | Close the menu |

Data Tables

The Table component provides sortable, searchable, and paginated data tables. Pass data/columns programmatically, or omit them and it reads an existing <table>'s thead/tbody markup inside the container instead.

const table = new Table('#my-table', {
    columns: [
        { key: 'name',   label: 'Name',   sortable: true },
        { key: 'role',   label: 'Role',   sortable: true },
        { key: 'status', label: 'Status', sortable: false },
    ],
    data: [
        { name: 'Alice Müller', role: 'Admin',  status: 'Active' },
        { name: 'Bob Schmitt',  role: 'Editor', status: 'Pending' },
    ],
    pageSize: 10,
});

table.setData(newData);       // replace data and re-render
table.setColumns(newColumns); // replace columns and re-render
table.getData();              // current filtered + sorted data
table.destroy();              // remove listeners and clear container

| Option | Type | Default | Description | |---|---|---|---| | data | TableRow[] | [] | Row data, each an object keyed by column key | | columns | TableColumn[] | [] | Column definitions: { key, label, sortable? } | | pageSize | number | 10 | Rows shown per page |

Date Picker

The DatePicker component provides a calendar interface for date selection. Supports single date or date range modes, an optional selectable date range via min/max, customizable locales, and mobile-responsive design.

DatePicker Parameters

| Parameter | Type | Description | |---|---|---| | input | HTMLInputElement | string | The input element to attach the date picker to | | options | DatePickerOptions | Configuration options for the date picker (see options table) | | currentDate | Date | The current date to be displayed; defaults to today's date | | selectedDate | Date | The selected date; defaults to null | | rangeStart | Date | The start of a selected date range; defaults to null | | rangeEnd | Date | The end of a selected date range; defaults to null | | viewYear | number | The year currently displayed; defaults to the current year | | viewMonth | number | The month currently displayed; defaults to the current month | | viewMode | ViewMode | The view mode ('days' | 'months' | 'years'); defaults to 'days' | | yearRangeStart | number | The start of the year range; defaults to this year |

DatePickerOptions

| Option | Type | Default | Description | |---|---|---|---| | mode | string | 'single' | Mode of the date picker, either 'single' or 'range' | | startDay | number | 0 | Start day of the week (0 = Sunday, 1 = Monday, etc.) | | min | Date | — | Earliest selectable date; dates, months, and years fully before it are disabled | | max | Date | — | Latest selectable date; dates, months, and years fully after it are disabled | | locales | DatePickerLocales | — | Locales object containing a days array and a months array with localized names | | format | (date: Date) => string | — | Function to format the date for display; defaults to 'YYYY-MM-DD' | | onSelect | (date: Date \| DateRange) => void | — | Callback when a date is selected |

Tree Component

The TreeComponent renders hierarchical data as an expandable/collapsible tree. Supports file/folder icons, selection, and programmatic expand/collapse.

import { TreeComponent, TreeNode } from '@dodlhuat/basix/js/tree.js';

const data = [
    new TreeNode('src', 'folder', [
        new TreeNode('index.ts'),
        new TreeNode('components', 'folder', [
            new TreeNode('Button.ts'),
            new TreeNode('Modal.ts'),
        ]),
    ]),
];

const tree = new TreeComponent('#my-tree', data, {
    onSelect: (node) => console.log('selected:', node.label),
});

tree.expandAll();
tree.collapseAll();
tree.selectNode(data[0]);
tree.getSelectedNode();          // TreeNode | null
tree.findNodeByLabel('Modal.ts'); // TreeNode | null
tree.destroy();

new TreeComponent(elementOrSelector, data, options)options only has onSelect?: (node: TreeNode) => void.

TreeNode

new TreeNode(label, type = 'file', children = [])

| Property | Type | Description | |---|---|---| | label | string | The node's label | | type | NodeType | 'file' or 'folder' | | children | TreeNode[] | Child nodes (folders only) | | expanded | boolean | Whether a folder node is currently expanded | | selected | boolean | Whether the node is currently selected |

File Uploader

The FileUploader component provides drag-and-drop file upload functionality with progress indication. Supports file validation (size, type), multiple files, and upload cancellation. It hydrates existing markup rather than rendering its own — the container must already contain .file-input, .drop-zone, .file-list, and .upload-btn.

<div id="my-uploader">
    <input type="file" class="file-input" multiple hidden/>
    <div class="drop-zone">
        <div class="drop-zone-content">
            <p class="primary-text">Click to upload or drag and drop</p>
            <p class="secondary-text">PNG, JPG, GIF, SVG (max 5 MB)</p>
        </div>
    </div>
    <div class="file-list"></div>
    <div class="actions">
        <button class="upload-btn" disabled>Upload Files</button>
    </div>
</div>
const uploader = new FileUploader('#my-uploader', {
    uploadUrl:    'https://api.example.com/upload',
    maxFileSize:  5 * 1024 * 1024,          // 5 MB in bytes
    allowedTypes: ['image/png', 'image/jpeg'],
});

// Events fired on the container element
document.getElementById('my-uploader').addEventListener('upload-completed', (e) => {
    console.log('All uploads done', e.detail); // { fileCount, files, results }
});

document.getElementById('my-uploader').addEventListener('file-validation-error', (e) => {
    console.warn('Rejected:', e.detail.file.name, '—', e.detail.reason); // 'size' | 'type'
});

uploader.destroy();

| Option | Type | Default | Description | |---|---|---|---| | uploadUrl | string | 'https://httpbin.org/post' | Endpoint the files are uploaded to via XHR | | maxFileSize | number | — | Max file size in bytes; larger files are rejected with a file-validation-error | | allowedTypes | string[] | — | Allowed MIME types; other files are rejected with a file-validation-error |

Virtual Dropdown

Virtual Dropdown is a performant, virtualized dropdown component that efficiently renders large option lists by only drawing visible items in the DOM. Supports single and multi-select modes, built-in search/filtering, keyboard navigation, and configurable item height and render limits — making it ideal for scenarios with hundreds or thousands of selectable options.

const dropdown = new VirtualDropdown({
    container:    '#my-dropdown',    // CSS selector or element
    options:      [                  // array of { value, label }
        { value: 'a', label: 'Option A' },
        { value: 'b', label: 'Option B' },
        // …up to 10,000+ items
    ],
    placeholder:  'Select…',
    searchable:   true,
    multiSelect:  true,
    onSelect: (values) => console.log('Selected:', values),
});

dropdown.getValue();            // Array<string | number>
dropdown.setValue(['a', 'b']);
dropdown.clearSelection();
dropdown.destroy();

| Option | Type | Default | Description | |---|---|---|---| | container | HTMLElement | string | — | Target container element or CSS selector | | options | DropdownOption[] | — | Array of { value, label } selectable options | | multiSelect | boolean | false | Allow selecting more than one option | | searchable | boolean | false | Shows a search input to filter options | | placeholder | string | 'Select...' | Trigger text shown when nothing is selected | | renderLimit | number | 20 | Max options rendered into the DOM at once (virtualization window) | | itemHeight | number | 40 | Row height in px, used to compute the virtual scroll | | onSelect | function | — | Callback (selectedValues) => void fired on selection change | | selectedLabel | function | "N item(s) selected" | Callback (count) => string formatting the trigger text in multi-select mode |

Group Picker

The GroupPicker component enables hierarchical group and subgroup selection. Users can either select an entire parent group (covering all subgroups) or pick individual subgroups. Groups without children are directly selectable. Selecting all subgroups automatically promotes to a parent selection. Includes debounced search with match highlighting, expand/collapse, and a chip-based selection summary.

<div id="group-picker-demo"></div>
const data: GroupData[] = [
  {
    id: 'gruppenspiele', label: 'Gruppenspiele',
    subgroups: [
      { id: 'grossgruppe', label: 'Grossgruppenspiele' },
      { id: 'trinkspiele', label: 'Trinkspiele' },
    ]
  },
  { id: 'schach', label: 'Schach' }, // no subgroups — directly selectable
];

const picker = new GroupPicker('#group-picker-demo', data, {
  onSelectionChange: (selection) => console.log(selection),
});

GroupData

| Property | Type | Description | |---|---|---| | id | string | Unique identifier for the group | | label | string | Display name | | subgroups | SubgroupData[] | Optional array of { id, label } child items |

GroupPickerOptions

| Option | Type | Default | Description | |---|---|---|---| | onSelectionChange | function | — | Callback fired on selection change with GroupPickerSelection | | searchPlaceholder | string | 'Gruppen durchsuchen...' | Placeholder text for the search input | | selectAllLabel | string | 'Alle' | Label for the select-all button | | deselectLabel | string | 'Abwahlen' | Label shown when a parent group is selected | | emptyLabel | string | 'Keine Ergebnisse' | Shown when search yields no results | | selectionPlaceholder | string | 'Noch keine Auswahl getroffen' | Placeholder in the selection summary area |

Public API

| Method | Description | |---|---| | getSelection() | Returns { parentGroups: string[], subgroups: { groupId, subgroupId }[] } | | setSelection(selection) | Programmatically set the selection state | | clearSelection() | Clear all selections | | expandAll() | Expand all groups | | collapseAll() | Collapse all groups | | destroy() | Remove event listeners and clear the DOM |

Time Span Picker

The TimeSpanPicker component provides a paired start/end time input for selecting a time range.

const picker = new TimeSpanPicker('my-container', {
    defaultStart: '09:00',
    defaultEnd:   '17:00',
    fromString:   'Von',   // optional — defaults to 'From'
    toLabel:      'Bis',   // optional — defaults to 'To'
    onChange: (start, end) => console.log(start, end),
});

picker.getValue();            // { start: '09:00', end: '17:00' }
picker.setValue('10:00', '18:00');
picker.reset();
picker.isValid();             // boolean
picker.destroy();

Color Picker

The ColorPicker component renders a canvas-based HSB color picker with a hue slider and hex/RGB inputs. It builds its own DOM inside any container element and is fully mobile-friendly — drag gestures on the color field do not trigger page scroll.

import { ColorPicker } from '@dodlhuat/basix/js/color-picker.js';

const picker = new ColorPicker('#my-picker', {
    value: '#3d63dd',
    onChange: (hex, rgb) => {
        console.log(hex);                    // '#3d63dd'
        console.log(rgb.r, rgb.g, rgb.b);   // 61, 99, 221
    },
});

picker.getValue();           // current hex string, e.g. '#3d63dd'
picker.setValue('#ff6b6b');  // set color programmatically
picker.destroy();            // removes listeners, disconnects ResizeObserver, clears DOM
<!-- Any empty container works — the component builds its own markup inside -->
<div id="my-picker"></div>

| Option | Type | Default | Description | |---|---|---|---| | value | string | '#ff0000' | Initial hex color (e.g. '#3d63dd'). Must be a 6-digit hex string. | | onChange | function | — | Called on every color change: (hex: string, rgb: { r, g, b }) => void |

Select

The Select component wraps a native <select> element with custom Basix styling. Supports single and multi-select.

<select id="my-select">
    <option value="a">Option A</option>
    <option value="b">Option B</option>
</select>
const sel = new Select('#my-select');
sel.value();   // returns selected value string, or string[] for multi-select
sel.destroy(); // removes the document click listener

// Static convenience form — returns a cleanup function to remove the document listener
const cleanup = Select.init(document.querySelector('#my-select'));
cleanup?.(); // call when the select is removed from the DOM

Code Viewer

The CodeViewer component renders syntax-highlighted code blocks inside any container. Supports JavaScript, HTML, and CSS.

const viewer = new CodeViewer('#output', '<div class="card">Hello</div>', 'html');
new CodeViewer('#output', 'const x = 42;', 'javascript');
new CodeViewer('#output', '.card { padding: 1rem; }', 'css');

viewer.destroy(); // removes event listeners and clears the container

Editor

The Editor component provides a contenteditable rich-text editing area with undo/redo, word count, and an optional side panel showing the raw HTML source and a live preview. Internal elements are located by data-editor attributes scoped to a root container — no globally unique IDs required, which makes multiple editors on the same page possible.

<div class="editor" id="my-editor">
    <div class="editor-toolbar">
        <button data-editor-action="undo" title="Undo">↩</button>
        <button data-editor-action="redo" title="Redo">↪</button>
        <button data-cmd="bold">B</button>
        <button data-cmd="italic">I</button>
        <button data-editor-action="link">Link</button>
        <!-- … -->
    </div>
    <div class="editor-body">
        <div class="editor-main">
            <div data-editor="editable" class="editable" contenteditable="true"></div>
        </div>
        <!-- Full mode: add side panel with code + preview -->
        <div data-editor="side-panel" class="editor-side">
            <div class="side-tabs">
                <button class="side-tab active" data-tab="code-panel">HTML</button>
                <button class="side-tab" data-tab="preview-panel">Preview</button>
            </div>
            <div class="side-panels">
                <div class="side-panel active" data-editor="code-panel">
                    <textarea data-editor="code"></textarea>
                </div>
                <div class="side-panel" data-editor="preview-panel">
                    <div data-editor="preview" class="preview-content"></div>
                </div>
            </div>
        </div>
    </div>
    <div class="editor-footer">
        <span data-editor="wordcount">0 words</span>
    </div>
</div>
// Single editor
new Editor({ root: '#my-editor' });

// Simple mode — only requires [data-editor="editable"], hides the side panel
new Editor({ root: '#my-editor', simple: true });

// Multiple editors on the same page
const editor1 = new Editor({ root: '#editor-1' });
const editor2 = new Editor({ root: '#editor-2' });

editor1.destroy();

Options

| Option | Type | Default | Description | |---|---|---|---| | root | string | HTMLElement | document.body | Root container. Required when multiple editors coexist on the same page. | | simple | boolean | false | Simple mode requires only [data-editor="editable"]. Full mode additionally requires code, preview, side-panel, code-panel, and preview-panel elements. |

data-editor elements

| Value | Element | Required | |---|---|---| | editable | contenteditable div — the writing area | always | | code | <textarea> — HTML source view | full mode | | preview | <div> — live preview | full mode | | side-panel | Side panel container | full mode | | code-panel | Code tab panel (matches data-tab="code-panel") | full mode | | preview-panel | Preview tab panel | full mode | | wordcount | Word count display | optional | | image-file | <input type="file"> for image insertion | optional |

data-editor-action buttons

| Value | Description | |---|---| | undo | Triggers undo | | redo | Triggers redo | | link | Opens URL prompt for link insertion | | image | Triggers the image file picker | | save | Downloads content as an HTML file (Ctrl+S) | | clear | Clears all content | | clean | Strips formatting from the current selection | | toggle-code | Shows / hides the HTML side panel | | apply-code | Applies the HTML source panel content to the editable area | | sanitize-code | Sanitizes the HTML source, then applies it to the editable area | | minify-code | Minifies the HTML source (removes whitespace and newlines) |

Cus