Skip to content

DependencyCodeEditorLand/Jelly

 
 

Repository files navigation

Jelly UI

Jelly UI is a zero-runtime-dependency Web Components library for soft, tactile product interfaces. It combines real form controls — native focus, keyboard behavior, standard events and FormData participation — with soft-body physics surfaces, so every press, toggle and drag settles with a wobble.

It is written in strict TypeScript, bundled with Vite into a single ESM file (dist/jelly.js) plus type declarations, and covered by real-browser tests (Vitest + Playwright). Consumers still get the whole library in one <script> tag — the build is ours, not theirs.

<script type="module" src="https://jelly-ui.com/package.js"></script>

<jelly-theme mode="auto">
  <jelly-button variant="mint">Publish</jelly-button>
  <jelly-input name="email" type="email" placeholder="you@example.com"></jelly-input>
  <jelly-switch checked>Notifications</jelly-switch>
</jelly-theme>

package.js is the public entry point; it re-exports the built bundle (dist/jelly.js), which registers every component and carries every named export for JavaScript consumers. Self-hosting? Serve package.js next to a dist/ you built with npm run build.

Developing locally

npm install
npm run build      # emit dist/jelly.js + dist/jelly.d.ts
npm run docs       # generate the docs data (docs/content/data.js, untracked)
npm run serve      # static server → http://localhost:4173

For an inner loop, run the watch build and the server side by side — the bundle rebuilds on every save; reload the page:

npm run dev        # vite build --watch  (rebuilds dist/ on change)
npm run serve      # in a second terminal
  • / — the showcase: scroll through every component live, with a sticky preview stage
  • /api — the API reference: every attribute, property, event, slot and token

Scripts

Script What it does
npm run build Bundle src/jelly.tsdist/jelly.js (+ .d.ts) with Vite
npm run build:site Build + docs, then assemble the Pages site into _site/
npm run dev Rebuild the bundle on every change (vite build --watch)
npm run serve Serve the static site with the dependency-free dev server
npm run typecheck tsc --noEmit in strict mode
npm test Run the real-browser test suite (Vitest + Playwright Chromium)
npm run docs Regenerate the API docs data from the component sources

Project structure

docs/showcase/index.html      Showcase page: one interactive playground per component
docs/showcase/index.js        Showcase renderer (controls, live code, scroll choreography)
docs/showcase/playgrounds.js  Showcase demo config (preview markup, controls, bindings)
docs/api/index.html           API reference page: aligned tables for every attribute/event/token
docs/api/index.js             Reference renderer (sidebar and aligned API tables)
docs/shared/common.js         Shared docs behavior (theme + motion toggles)
docs/content/content.js       Curated component docs (edit this to document components)
docs/content/data.js          Generated docs data the pages render (do not edit)
docs/lottie/index.js          Lazy-loads the hero + outro Lottie illustrations
docs/{shared/site,showcase/showcase,api/reference}.css   Page chrome and per-page styles
package.js                    Public entry point (re-exports the built bundle, dist/jelly.js)
dist/jelly.js                 Built ESM bundle (generated by `npm run build`; gitignored)
_site/                        Assembled Pages site (generated by `npm run build:site`; gitignored)
scripts/build-site.mjs        Assembles _site/ — builds, runs docs, then copies just the site
scripts/build-docs-data.mjs   Reconciles docs/content/content.js against the manifest → data.js
vite.config.ts                Library-mode build (single ESM bundle + bundled .d.ts)
vitest.config.ts              Real-browser test runner (Playwright Chromium)
tsconfig.json                 Strict TypeScript configuration
custom-elements-manifest.config.mjs   Manifest analyzer config (docs source of truth)
server.mjs                    Tiny dependency-free static server (mirrors the built site)
src/jelly.ts                  Barrel module — registers every component
src/theme/                    Design tokens (light + dark), variants, theme runtime
src/core/                     Soft-body physics: config, the JellyBody simulation, the shared loop
src/element/                  Base class for canvas-backed components (+ its shape/paint types)
src/anchor/                   Overlay helpers (positioning, scroll lock, modality, motion)
src/utilities/                Shared utilities (events, ids, RTL, keyboard, size, motion)
src/icons/                    Icon renderer (index.ts) over src/icons/*.svg (typed IconName union)
src/styles/                   Shared CSS partials (base + variants), imported ?inline
src/testing/                  Test-only helpers (mount / settle) shared by the component tests
src/components/               One directory per component: <name>/index.ts + <name>.css + <name>.test.ts
.github/workflows/            GitHub Pages deployment (builds, tests, then publishes _site/)

Components

Area Components
Theming jelly-theme
Actions jelly-button, jelly-icon-button
Forms jelly-input, jelly-textarea, jelly-select + jelly-option, jelly-checkbox, jelly-radio + jelly-radio-group, jelly-switch, jelly-slider, jelly-range, jelly-segmented + jelly-segment, jelly-otp, jelly-label
Feedback jelly-alert, jelly-badge, jelly-progress, jelly-spinner, jelly-skeleton, jellyToast() + jelly-toaster
Surfaces jelly-card, jelly-chip, jelly-kbd, jelly-divider, jelly-collapsible, jelly-accordion
Navigation jelly-tabs + jelly-tab-panel, jelly-breadcrumbs, jelly-pagination, jelly-resizable
Overlays jelly-tooltip, jelly-popover, jelly-menu + jelly-menu-item, jelly-dialog, jelly-drawer

The full API reference — every attribute, property, event, slot, shadow part, CSS custom property and keyboard map — lives on the API reference (/api), with live examples on the showcase (/), both generated from the component sources (see Documenting components).

Theming

All colors flow through CSS custom properties (--jelly-color-*) defined in src/theme/, with verified WCAG AA light and dark values. Three ways to theme:

1. The theme provider — scope a mode (or accent) to any subtree:

<jelly-theme mode="dark">
  <jelly-card>Dark-token subtree</jelly-card>
</jelly-theme>

<jelly-theme mode="auto" accent="#7C3AED">
  <jelly-slider></jelly-slider>
</jelly-theme>

mode="auto" (the default) follows the operating system.

2. Document-wide mode — from JavaScript:

import { setThemeMode } from './package.js';

setThemeMode('dark');   // 'light' | 'dark' | 'auto'

3. Token overrides — any token, any scope, plain CSS:

:root {
  --jelly-color-background-accent: #7C3AED;
  --jelly-color-background-azure:  #4F46E5;
}

Per-instance overrides still work too:

<jelly-button style="--jelly-fill: #5A5A5A; --jelly-label: #FFFFFF">Custom</jelly-button>

Variants

Fillable components accept variant="white | rose | amber | azure | mint | platinum | graphite". Variants map to theme tokens, so they adapt to dark mode automatically. Without a variant, filled controls use the shared accent fill (--jelly-color-background-accent / --jelly-color-foreground-on-accent).

Key shared custom properties

Property Use
--jelly-color-* The global token set (surfaces, text, hues, ring, accent)
--jelly-font-display / -text / -mono The three shared font stacks
--jelly-fill Jelly surface fill of one instance
--jelly-label Text/label color of one instance
--jelly-accent Active tint for slider, select, progress, …
--jelly-on Active tint for checkbox, radio, switch, chip
--jelly-ring Focus ring color for canvas-backed controls
--jelly-ring-width / -gap Focus ring thickness and offset
--jelly-shadow-raised / -overlay Elevation shadows on cards and overlays

Each component also exposes its own --jelly-<component>-* knobs (padding, radius, fonts…) — see its docs section.

Dark mode

Dark mode is token-driven: by default components follow prefers-color-scheme; force it with <jelly-theme mode="dark">, the data-jelly-mode="dark" attribute on <html>, or setThemeMode('dark'). Canvas-painted surfaces listen for theme changes and repaint themselves.

Right-to-left

Components use CSS logical properties and direction-aware interaction: set dir="rtl" on any ancestor and layout mirrors, horizontal arrow keys flip to match reading direction (sliders, radios, segmented controls, resizable splitters), switches travel toward the inline-end, overlays accept logical placement="start|end" and toasts slide in from the inline-end. One-time-code inputs stay left-to-right by design, like the codes themselves.

Accessibility

  • WCAG AA contrast for the text, surface and variant tokens in both light and dark.
  • Native focusable controls inside shadow roots; visible (jiggling!) focus rings, with forced-colors fallbacks.
  • Correct ARIA patterns: combobox/listbox select, tablist tabs, menu with roving focus, modal dialogs with scroll lock + background inerting + focus restore, window-splitter resizable panes.
  • Full keyboard support, including Shift+Arrow fast-stepping on sliders.
  • prefers-reduced-motion swaps wobble for calm, instant feedback.
  • Form controls participate in FormData via ElementInternals and accept a label attribute for accessible naming.

Forms and events

Form controls behave like native ones and emit composed events:

const plan = document.querySelector('jelly-select');

plan.addEventListener('change', () => console.log(plan.value));

const data = new FormData(myForm); // jelly controls are included

Buttons with type="submit" / type="reset" drive the closest light-DOM <form>. Interaction events stay unprefixed: chips emit cancelable remove, collapsibles emit toggle, menus emit select, and OTP emits change on a complete code (plus a complete alias that carries the value).

Documenting components

The docs are automatic where it counts. Every component's public surface — attributes, events, slots, shadow parts — is read from a Custom Elements Manifest that cem analyze generates from the TypeScript sources and their JSDoc. Curated prose (summaries, descriptions, examples, keyboard maps) lives in docs/content/content.js. Running npm run docs reconciles the two and writes the generated docs/content/data.js the pages render — warning aloud if a component gains or loses an attribute/event/slot/part the docs don't reflect, so the reference can't silently drift from the code. docs/showcase/playgrounds.js configures each component's live demo: the preview markup, the values the scroll-through showcase steps and the events that write back into the table.

The full recipe — fixing a typo, adding a component, changing a demo — is in docs/README.md.

Deploying to GitHub Pages

.github/workflows/deploy-pages.yml type-checks and tests, then runs npm run build:site to assemble a clean _site/ (the library bundle, the docs assets and the two pages — the API page as api/index.html, so it serves at /api) and publishes only that on every push to main (or manually from the Actions tab). One-time setup: Settings → Pages → Source → "GitHub Actions".

How the jelly works

Each canvas-backed component owns a JellyBody: a closed ring of spring-coupled membrane points around a rounded rectangle, with pressure-based volume preservation, pointer-following press targets, tilt and perspective. A single shared requestAnimationFrame loop steps every live body — in fixed-size substeps so the simulation stays stable at any frame rate — and parks itself when everything is at rest, so an idle page costs nothing. If a body ever produces a non-finite number it resets itself instead of wedging.

prefers-reduced-motion bypasses the impulses entirely.

Browser support

Current Chrome, Edge, Safari and Firefox. Jelly UI leans on Custom Elements, Shadow DOM, ElementInternals, ResizeObserver, Canvas 2D, color-mix(), and CSS logical properties.

License

MIT — see LICENSE. Bundles Fluent System Icons (also MIT) as standalone SVGs in src/icons/, rendered by src/icons/index.ts.

About

Soft, tactile components for product interfaces. Native forms, keyboard access, standard events and just one module import.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 83.0%
  • CSS 14.2%
  • JavaScript 2.8%