Skip to content

Guseyn/e-ui

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

v 1.0.0

UI library for SaaS applications. e-ui provides a design system (CSS) and interactive custom elements (JavaScript) that work alongside EHTML — a declarative, no-build-step HTML framework.

  • CSS: static/css/e-ui.css — design tokens, utilities, typography, layout, forms, and component styles
  • JS: static/js/e-ui/ — custom elements and EHTML template extensions
  • Examples: static/html/ — runnable demo pages (start the dev server with npm run server)

Quick start

e-ui is designed to be used with EHTML. EHTML activates templates, binds data, and runs actions; e-ui components listen for EHTML's ehtml:activated event before initializing. Import e-ui modules after #ehtml/main so custom elements are registered when EHTML activates the page.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My App</title>
  <script type="importmap">
  {
    "imports": {
      "#ehtml/": "/js/ehtml/",
      "#ehtml/main": "/js/ehtml/main.js",
      "#e-ui/": "/js/e-ui/"
    }
  }
  </script>
  <script type="module">
    import '#e-ui/e-toast.js'
    import '#e-ui/e-dialog.js'
    import '#ehtml/main'
  </script>
  <link rel="stylesheet" href="/css/e-ui.css">
</head>
<body>
  <!-- your markup -->
</body>
</html>
npm run server

Open http://localhost:8004/html/index.html for the example gallery.

Design tokens

Override CSS variables on :root to theme an app (for example, set --e-primary from your brand color):

:root {
  --e-primary: #3551A4;
  --e-bg: #f6f7fb;
  --e-surface-bg: #ffffff;
  --e-spacing-sm--e-spacing-3xl;
  --e-font-size-xs … --e-font-size-3xl;
  --e-sidebar-collapsed: 4.5rem;
  --e-sidebar-expanded: 20rem;
}

Utility attributes

Apply spacing, flex, color, and sizing without custom CSS. Works on any element or [is^="e-"]:

<div is="e-stack" data-gap="md" data-padding="lg" data-align-items="center">
  <span is="e-muted" data-font-size="sm">Subtitle</span>
</div>

Common utilities: data-padding, data-margin, data-gap, data-flex-direction, data-justify-content, data-align-items, data-width (xsxl, full), data-font-size, data-font-weight, data-color, data-background, data-border-radius, data-columns.


Typography

Styled text elements use the is attribute on native HTML tags. Only e-ui.css is required — no JS imports.

Live demo: typography-layout.html · Screenshot: typography.png

Headings

Use is="e-h" on any heading level. Size comes from the tag (h1h6).

<h1 is="e-h">Page title</h1>
<h2 is="e-h">Section title</h2>
<h3 is="e-h">Subsection</h3>
<h6 is="e-h" data-color="muted" data-text-case="upper">Eyebrow label</h6>

Paragraphs and quotes

<p is="e-p">Body copy uses the typography font stack and relaxed line height.</p>

<blockquote is="e-quote">
  A blockquote for testimonials or callouts.
</blockquote>

<pre is="e-pre">const greeting = 'Hello'</pre>

Text spans

is value Purpose
e-text Default body text color
e-muted Secondary / de-emphasized text
e-light Text on dark backgrounds
e-bold Bold emphasis
e-caption Small uppercase label
e-error, e-danger Error message color
e-helper Helper text below fields (block)
<span is="e-text">Default text</span>
<span is="e-muted">Muted description</span>
<span is="e-bold">Important</span>
<span is="e-caption">Section label</span>
<span is="e-error">This field is required</span>
<span is="e-helper">Shown below an input</span>

Labels, tags, and chips

<span is="e-badge">3</span>
<span is="e-tag">Draft</span>
<span is="e-chip">Removable chip <img src="/images/close.svg" alt=""></span>

span[is="stack"] is an inline vertical stack for compact multi-line labels:

<span is="stack">
  <span is="e-bold">Jane Doe</span>
  <span is="e-muted">Instructor</span>
</span>

Inline code and highlight

<p is="e-p">Run <code is="e-code">npm run server</code> locally.</p>
<p is="e-p">Search for <mark is="e-mark">keyword</mark> in results.</p>

Links

<a is="e-link" href="#">Text link</a>
<a is="e-link" data-underlined href="#">Underlined link</a>

Dividers

<hr is="e-divider">
<hr is="e-divider" data-dashed>
<hr is="e-divider" data-primary-color>
<hr is="e-divider-text" data-text="Or continue with">

Layout

Flex, grid, and page-structure primitives. Combine with utility attributes (data-gap, data-padding, data-width, etc.).

Live demo: typography-layout.html · Screenshot: layout.png

Flex and grid

is value Element Behavior
e-stack div Column flex container
e-row div Row flex container (stacks on mobile unless data-keep-flex-direction-row-in-mobile)
e-grid div 3-column grid (1 column below 980px)
e-box div Plain block wrapper
e-spacer div Flex grow spacer
<div is="e-stack" data-gap="md">
  <div is="e-row" data-gap="sm" data-justify-content="space-between">
    <span is="e-bold">Title</span>
    <div is="e-spacer"></div>
    <button data-primary>Action</button>
  </div>
</div>

<div is="e-grid" data-gap="lg" data-columns="2">
  <div is="e-card" data-padding="lg">Column A</div>
  <div is="e-card" data-padding="lg">Column B</div>
</div>

Surfaces

<div is="e-card" data-padding="lg">Bordered card with shadow</div>
<div is="e-panel" data-padding="lg">Full-width elevated panel</div>
<div is="e-info" data-padding="md">Muted info notice box</div>

Page regions

<header is="e-header"><!-- fixed mobile header --></header>

<main is="e-main" data-centralized>
  <section is="e-section"><!-- content --></section>
</main>

<footer is="e-footer" data-justify-content="center">© 2026</footer>

data-centralized on e-main applies max-width and horizontal padding from --e-main-max-width.


CSS-only components

These patterns need only e-ui.css. They are separate from typography and layout primitives above.

Buttons

Live demo: forms-buttons.html · Screenshot: forms-buttons.png

<button data-primary>Solid</button>
<button data-primary data-fill="outlined">Outlined</button>
<button data-primary data-fill="danger">Danger</button>
<button is="e-with-icon" title="Add">
  <img src="/images/plus.svg" alt="">
</button>

<a is="e-link-button" href="#">Link button</a>
<a is="e-link-button" data-variant="outline" href="#">Outline link button</a>

Forms

Live demo: forms-buttons.html · Screenshot: forms-buttons.png

<form is="e-form" data-width="sm">
  <label>
    Email
    <input type="email" name="email" required>
  </label>
  <label data-flex-direction="row">
    <input type="checkbox" name="remember">
    Remember me
  </label>
  <label>
    Role
    <select name="role"><option>Admin</option></select>
  </label>
  <label>
    Notes
    <textarea name="notes" rows="3"></textarea>
  </label>
  <button data-primary type="submit">Save</button>
</form>

Use data-no-style on e-form to skip the card-like wrapper. Group fields with fieldset[is="e-fieldset"] and legend[is="e-legend"].

File inputs inside e-form pick up drag-and-drop styling automatically when generated by the e-file-upload template (see Custom elements).

Tables

<table is="e-table">
  <thead>
    <tr><th>Name</th><th>Role</th></tr>
  </thead>
  <tbody>
    <tr><td>Alice</td><td>Admin</td></tr>
  </tbody>
</table>

Lists

<ul is="e-list" data-type="bullet" data-divided="true">
  <li is="e-list-item">Item one</li>
  <li is="e-list-item" data-interactive="true">Clickable item</li>
</ul>

User avatar

Live demo: forms-buttons.html · Screenshot: user-avatar.png

Circular avatar with initials or background image. With EHTML, data-text is evaluated into the element on activation.

<div
  is="e-user-avatar"
  data-size="md"
  style="background-color: #3551A4;"
  data-text="JD">
</div>

<div
  is="e-user-avatar"
  data-size="md"
  style="background: url(/avatar.jpg) center center / cover no-repeat; color: transparent;"
  data-text="JD">
</div>

Size with data-size, data-width, or data-height (3xs3xl).

Tooltip

Live demo: forms-buttons.html · Screenshot: forms-buttons.png

e-tooltip is styled by CSS only — wrap the trigger element and set data-tip:

<e-tooltip data-tip="Copy URL" data-direction="to-right">
  <img src="/images/copy.svg" alt="Copy">
</e-tooltip>

Scrollable regions

Use the e-scrollable tag for overflow containers (also used internally by multiselect dropdown):

<e-scrollable data-show-scrollbar data-padding="sm">
  <!-- long content -->
</e-scrollable>

Keyboard hint appearance

Live demo: chips-kbd.html · Screenshot: forms-buttons.png

kbd[is="e-kbd"] is styled by CSS. To bind a global shortcut, import e-kbd.js — see Custom elements.

<kbd is="e-kbd">Ctrl + K</kbd>
<kbd is="e-kbd" data-absolute>⇧ + S</kbd>

Custom elements

These require JS module imports (before #ehtml/main) and EHTML activation. Most expose data-action, data-onopen, or data-onclose strings evaluated by EHTML.

Module Element Description
e-toast.js <e-toast> Toast notifications
e-dialog.js dialog[is="e-dialog"] Modal dialogs
e-sidebar.js <e-sidebar> App sidebar
e-tab.js <e-tabs>, <e-tab> Tab navigation
e-kbd.js kbd[is="e-kbd"] Keyboard shortcuts via data-action
e-kbd-graph.js <e-kbd-graph> Arrow-key focus graph
e-selectable-chip.js <e-selectable-chip> Toggle chip
e-autoclick-button.js button[is="e-autoclick"] Auto-click on activation
e-file-upload-template.js <template is="e-file-upload"> File upload drop zone
e-multiselect-dropdown-template.js <template is="e-multiselect-dropdown"> Searchable multiselect
e-week-picker-template.js <template is="e-week-picker"> Week navigation

e-multiselect-dropdown requires e-kbd.js to be imported before its template module.

e-toast

Live demo: dialog-toast.html · Screenshot: toast.png

<e-toast
  data-type="success"
  data-position="top-right"
  data-hide-after-n-seconds="5"
  data-close-icon="/images/close.svg"
></e-toast>
document.querySelector('e-toast').open('Saved successfully!')

API: .open(content?, delay?), .close()

Attribute Values
data-type success, error, warning, info
data-position top-left, top-right, bottom-left, bottom-right
data-hide-after-n-seconds Auto-close delay
data-icon, data-close-icon Image URLs

e-dialog

Screenshot: dialog.png

<dialog id="my-dialog" is="e-dialog" data-size="normal" data-close-icon="/images/close.svg">
  <div data-padding="lg">
    <h2 is="e-h">Confirm</h2>
    <p is="e-p">Are you sure?</p>
    <button data-primary onclick="document.querySelector('#my-dialog').close()">OK</button>
  </div>
</dialog>

API: .showModal(), .close() — backdrop click and Escape close; body scroll is locked while open.

Attribute Description
data-close-icon Close button image URL
data-open-on-load Open when activated
data-onopen, data-onclose EHTML action strings
data-size small, normal, large, full

e-sidebar

Live demo: sidebar-tabs.html · Screenshot: sidebar.png

<div is="overlay"></div>
<header is="e-header">Mobile header</header>

<e-sidebar
  data-side="left"
  data-mobile-header='header[is="e-header"]'
  data-mobile-menu-icon="/images/menu-icon.svg"
  data-mobile-body-overlay="div[is='overlay']"
>
  <header><!-- logo --></header>
  <nav>
    <a href="#" data-selected="true"><img src="/icon.svg"><span>Home</span></a>
  </nav>
  <footer><!-- user --></footer>
</e-sidebar>

API: .open(), .close()

e-tabs / e-tab

Live demo: sidebar-tabs.html · Screenshot: tabs.png

<e-tabs data-apply-hash-navigation>
  <e-tab data-title="Overview"><p is="e-p">Tab 1</p></e-tab>
  <e-tab data-title="Settings"><p is="e-p">Tab 2</p></e-tab>
</e-tabs>

API: e-tabs.selectTab(index)

e-kbd

Import e-kbd.js. Text content defines the shortcut; data-action runs EHTML actions on match.

Live demo: chips-kbd.html · Screenshot: kbd.png

<kbd is="e-kbd" data-action="document.querySelector('e-toast').open('Shortcut!')">
  Ctrl + K
</kbd>
Attribute Description
data-action EHTML action string
data-trigger-in-inputs Fire while focus is in an input
data-absolute Badge positioning

e-kbd-graph

<e-kbd-graph data-first-element="button:first-of-type">
  <button data-right="button:nth-of-type(2)" data-click-on-enter>One</button>
  <button data-left="button:first-of-type">Two</button>
</e-kbd-graph>

Link focus with data-left, data-right, data-up, data-down.

e-selectable-chip

Live demo: chips-kbd.html · Screenshot: chips.png

<e-selectable-chip data-value="jazz" data-selected="true">Jazz</e-selectable-chip>

API: .select(), .unselect()

e-autoclick

<button is="e-autoclick" data-condition-to-click="true" data-primary>Submit</button>

Clicks itself once when EHTML activates and the condition is truthy.

EHTML templates

Templates use data-internal-state for dynamic data and are replaced with interactive UI on activation.

e-file-upload

Live demo: file-upload.html · Screenshot: file-upload.png

<template
  is="e-file-upload"
  data-name="avatar"
  data-accept="image/png, image/jpeg"
  data-icon-src="/images/upload-image.svg"
  data-label-text="Profile photo"
  data-action-text="Click or drag a file here"
  data-details-text="PNG or JPG up to 2 MB"
  data-max-size-in-mb="2"
  data-show-errors-in-toast
  data-set-height
></template>

Use multiple, data-max-number-of-files, and data-required as needed. When using data-show-errors-in-toast, define window.showErrorToast(message) in your page script.

e-multiselect-dropdown

Live demo: multiselect-dropdown.html · Screenshot: multiselect.png

<template
  is="e-multiselect-dropdown"
  data-internal-state="${{
    values: ['Jazz', 'Rock', 'Classical'],
    selectedValues: ['Jazz'],
    valueDisplayFunction: function (internalState, value) { return value }
  }}"
  data-name="genres"
  data-label="Select genres"
  data-selected-chip-close-icon="/images/close.svg"
>
  <template></template>
</template>

Add data-always-on to keep the checkbox list visible without a search-triggered dropdown.

e-week-picker

<template
  is="e-week-picker"
  data-prev-icon="/images/chevron-left.svg"
  data-next-icon="/images/chevron-right.svg"
  data-on-week-change="/* EHTML actions */"
></template>

Development

# Start static file server
npm run server

# Sync EHTML source from sibling repo
npm run ehtml:update

# Sync nodes.js helpers
npm run nodes:update

License

MIT

About

Custom UI library for EHTML to build your application. Just copy and paste HTML and enjoy.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors