doticca-ui
v0.2.1
Published
Framework-agnostic, zero-dependency UI component suite (DateTimePicker, SelectPicker) with a shared overlay core, design tokens, and touch-first UX.
Downloads
338
Maintainers
Readme
doticca-ui
A small suite of production-grade, framework-agnostic UI components written in plain JavaScript — zero dependencies, no virtual DOM, and no build step required. It currently ships two components, both built on one shared core and themed entirely through CSS custom properties:
- DateTimePicker — date, time, datetime, and range modes, with an iOS-style inertial wheel on touch devices.
- SelectPicker — a native
<select>replacement: single, multi, searchable, grouped, virtualized, and async.
Each component is a self-contained class you point at an existing <input> or <select>. The element is enhanced in place while still submitting normally inside a form. Desktop gets a popover; touch devices get a native-feeling bottom sheet automatically. Light/dark themes auto-detect via prefers-color-scheme.
Documentation
The documentation site is the landing page of this app. Run the dev server (below) and open http://localhost:3000.
The docs are authored as a static site under public/docs/ and served at the root / (and at /docs) via a rewrite in next.config.mjs. They cover:
- Introduction, Getting started, and Architecture
- Per-component references (DateTimePicker, SelectPicker) with live, interactive examples
- Theming, Accessibility, and Form integration
- Examples and a guide to extending the system
Every example on the docs site runs the real bundled library, so what you see is exactly what ships.
MCP server (for AI tools)
/mcp is a real Model Context Protocol server, so AI assistants (VS Code / Copilot, Cursor, Claude Desktop, LLM agents) can connect to it and answer "how do I use component X" questions without hallucinating. It speaks JSON-RPC over the Streamable HTTP transport via Vercel's mcp-handler.
All answers derive from a single source of truth — lib/mcp/registry.ts, which mirrors the real component DEFAULTS and the shared BaseComponent surface — so the docs never drift from the shipped components.
Connect a client
Point any MCP client at the deployed endpoint (Streamable HTTP):
{
"servers": {
"doticca-ui": { "url": "https://ui.doticca.com/mcp" }
}
}For stdio-only clients, bridge with mcp-remote:
{
"doticca-ui": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://ui.doticca.com/mcp"]
}
}Tools
| Tool | Description |
| --- | --- |
| list_components | List every component with taglines, modes, and features. |
| get_component | Full description, use cases, key behaviors, mobile vs desktop. |
| get_component_api | Options (type, default, allowed values), methods, events. |
| get_component_examples | Runnable usage examples with code. |
| search_docs | Ranked search across components, features, options, examples, theming. |
| recommend_usage | Recommended config + code for a component and intent (e.g. multi-select). |
| get_theming | Design-token theming system with light/dark values and overrides. |
Browsable REST mirror
The same data is also exposed as a plain JSON REST API (handy for quick curl checks or non-MCP crawlers):
| Endpoint | Description |
| --- | --- |
| GET /mcp/info | Discovery manifest: suite info, MCP tools, and REST endpoints. |
| GET /mcp/components | List all components with summaries. |
| GET /mcp/components/{name} | Full detail for one component. |
| GET /mcp/components/{name}/api | Options, methods, and events. |
| GET /mcp/components/{name}/examples | Usage examples with code. |
| GET /mcp/search?q={query} | Scored search across the suite. |
| GET /mcp/theming | Machine-readable design-token system. |
| GET /mcp/usage?component={name}&intent={intent} | Recommended config + code for an intent. |
curl "https://ui.doticca.com/mcp/usage?component=SelectPicker&intent=multi-select"
# => { "recommended": { "multiple": true, "searchable": true, "clearable": true }, ... }To add a future component, extend COMPONENTS in lib/mcp/registry.ts; every MCP tool and REST endpoint picks it up automatically.
Getting Started
Install dependencies and run the development server:
npm install
npm run dev
# or: yarn dev / pnpm devOpen http://localhost:3000 to view the documentation and live demos.
Installation
npm install doticca-uiThe package ships three files in dist/:
| File | Field | Use |
| --- | --- | --- |
| doticca-ui.esm.js | module | ESM build for bundlers (Vite, webpack, Rollup, Next.js). |
| doticca-ui.min.js | main | Minified IIFE for a plain <script> tag (global DoticcaUI). |
| doticca-ui.css | style | Single combined stylesheet for the whole suite. |
Usage with a bundler (import)
import { DateTimePicker, SelectPicker } from "doticca-ui"
import "doticca-ui/css"
// DateTimePicker — enhances an existing <input>
new DateTimePicker("#date", {
mode: "datetime", // "date" | "time" | "datetime" | "range"
use24Hour: false,
minuteStep: 5,
onChange: (value) => console.log(value),
})
// SelectPicker — replaces a native <select> (or empty placeholder element)
new SelectPicker("#country", {
options: [
{ label: "United States", value: "us" },
{ label: "Canada", value: "ca" },
],
searchable: true,
clearable: true,
})Usage in the browser (no build step)
<link rel="stylesheet" href="https://unpkg.com/doticca-ui/dist/doticca-ui.css" />
<script src="https://unpkg.com/doticca-ui/dist/doticca-ui.min.js"></script>
<input id="date" />
<select id="country"></select>
<script>
const { DateTimePicker, SelectPicker } = DoticcaUI
new DateTimePicker("#date", { mode: "date" })
new SelectPicker("#country", { searchable: true })
</script>Both components share the --dt-* CSS custom properties, so a single token set themes the entire suite, and light/dark auto-detects via prefers-color-scheme. See the Theming page in the docs for the full token reference.
API overview
Each component is new Component(target, options), where target is a CSS selector or element. Instances expose open(), close(), getValue(), setValue(v), setTheme(theme), and destroy(), and accept onChange / onOpen / onClose callbacks.
DateTimePicker — key options
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| mode | string | "date" | "date", "time", "datetime", or "range". |
| use24Hour | boolean | true | 24-hour vs 12-hour clock. |
| minuteStep / hourStep | number | 1 | Step granularity for time wheels. |
| minDate / maxDate | Date | null | Selectable bounds. |
| disabledDates | array | fn | null | Date[], "YYYY-MM-DD"[], or (date) => boolean. |
| displayFormat | string | null | e.g. "DD/MM/YYYY hh:mm A". |
| inline | boolean | false | Render inline instead of in an overlay. |
| touchUi | string | "auto" | "auto", true, or false (wheel bottom-sheet). |
| theme | string | "auto" | "auto", "light", or "dark". |
SelectPicker — key options
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| options | array | null | { label, value }[], optionally grouped. Plain business objects also work — see columns. |
| multiple | boolean | false | Multi-select with chips. |
| searchable | boolean | false | Inline search filter. |
| clearable | boolean | false | Show a clear control. |
| maxSelections | number | Infinity | Cap for multi-select. |
| virtualized | boolean | true | Virtualize long lists. |
| loadOptions | fn | null | Async loader (query) => Promise<options>. |
| placeholder | string | "Select…" | Empty-state text. |
| theme | string | "auto" | "auto", "light", or "dark". |
| columns | array | null | Rich column config (see below). Controls the visible grid layout and which fields are searchable. |
| groupBy | fn | null | (item) => string — derive group headers from a flat option list. |
| searchFields | string[] | null | Raw-object keys to include in search matching (even if not visible). |
| getSearchText | fn | null | (item) => string — highest-priority custom search haystack. |
| getDisplayValue | fn | null | (item) => string — text shown in the selected control. |
| renderItem | fn | null | (item) => Node\|string — full custom row body (alias: renderOption). |
| getAriaLabel | fn | null | (item) => string — custom per-option aria-label. |
SelectPicker — rich multi-column dropdowns
Each option can carry many fields — some visible as columns, some searchable-only. This powers business dropdowns like a supplier picker where the VAT/tax ID is searchable but not displayed.
Column config:
| Column key | Type | Notes |
| --- | --- | --- |
| key | string | Property read from the option object. |
| label | string | Header label (reserved for future header UI). |
| visible | boolean | false hides the column from the row but keeps it available for search. |
| searchable | boolean | Include this field in search matching. |
| width | string | CSS grid track size for the column (e.g. "1fr", "120px"). |
| align | string | "left", "center", or "right". |
| renderCell | fn | (value, item) => Node\|string — custom cell content. Strings are inserted as text (never HTML). |
Search source priority: getSearchText → searchFields → searchable columns → the option label/value. Numeric queries also match digits-only (so "6969" matches a VAT formatted "69-69-69").
new SelectPicker("#supplier", {
options: suppliers, // [{ id, name, vatNumber, balance, type }, ...]
searchable: true,
groupBy: (item) => item.name.charAt(0).toUpperCase(),
searchFields: ["name", "vatNumber"], // VAT searchable but hidden
getDisplayValue: (item) => item.name, // selected control shows only the name
columns: [
{ key: "name", label: "Name", visible: true, searchable: true, width: "1fr" },
{ key: "vatNumber", label: "VAT", visible: false, searchable: true },
{
key: "balance",
label: "Balance",
visible: true,
align: "right",
width: "120px",
renderCell: (value) => {
const el = document.createElement("span")
el.className = value > 0 ? "dt-positive" : value < 0 ? "dt-negative" : "dt-muted"
el.textContent = new Intl.NumberFormat("el-GR", { style: "currency", currency: "EUR" }).format(value)
return el
},
},
],
})renderItem stays fully supported and takes over the entire row body; when provided, columns is used only for searchFields/search config and does not override your custom row. All existing features — grouping, multi-select, virtualization, async loadOptions, and keyboard navigation — continue to work with rich columns.
The full, machine-readable API (every option, method, and event) is available from the docs site and the MCP server.
Building from source
The component source lives in src/. Rebuild the dist/ bundles with:
npm run build:libprepublishOnly runs this automatically before npm publish, so the published tarball always contains a fresh build. Only dist/ is published (files: ["dist"]).
Project structure
src/ Component source (core + components, framework-agnostic)
dist/ Published bundles: doticca-ui.esm.js / .min.js / .css
example.html Standalone HTML example that loads the built dist bundle
public/docs/ Static documentation site, served as the landing page
lib/mcp/ MCP registry (source of truth) + search/serve helpers
app/mcp/ MCP JSON API route handlers (AI-facing docs layer)
app/ Next.js app shell (hosts the docs)Built with v0
This repository is linked to a v0 project. You can continue developing by visiting the link below — start new chats to make changes, and v0 will push commits directly to this repo. Every merge to main will automatically deploy.
