Web Components · Vanilla JS · Bootstrap / Halfmoon

HTML, but Smarter.

Tables. Images. Inputs. Buttons.
Everything you write 80 lines of fetch + render + validate JS for —
now a single attribute-driven HTML tag.

smart-table.html

How much code does it replace?

We counted. Every attribute replaces something you'd otherwise wire up by hand — every time, for every project.

0
lines saved
building a sortable, paginated table from scratch
0
lines saved
adding lazy-load images with skeleton + fallback
0
lines saved
wiring a validated form input with error state
0
components
covering 90% of your UI primitives out of the box

Lines of code: Manual vs SmartComponents

avg 87% reduction
Component
Feature
Before
After
Saved
<smart-table>
Fetch + render + sort + paginate
~95 lines
1 tag
94 lines
<smart-table>
Delete row + confirm modal + toast
~40 lines
1 attr
39 lines
<smart-image>
Lazy load + shimmer skeleton + fade-in
~35 lines
1 tag
34 lines
<smart-image>
Fallback on error + retry + click preview
~30 lines
2 attrs
28 lines
<smart-input>
Input + validation + error message
~25 lines
1 tag
24 lines
<custom-button>
AJAX submit + loading + confirm dialog
~45 lines
1 tag
44 lines

Same result. A fraction of the code.

See exactly what each SmartComponent replaces — line by line, feature by feature.

Before — The usual way

~95 lines every time you need a paginated table

// 1. Wire up fetch + build query string
async function fetchUsers(page = 1, sort = 'name') {
  const res = await fetch(
    `/api/users/?page=${page}&sort=${sort}&limit=10`
  );
  const json = await res.json();
  renderTable(json.results, json.count, page);
}

// 2. Render rows manually
function renderTable(rows, total, page) {
  const tbody = document.querySelector('#tbl tbody');
  tbody.innerHTML = rows.map(r => `
    <tr>
      <td>${r.name}</td><td>${r.email}</td>
      <td><span class="${badgeClass(r.status)}">
           ${r.status}</span></td>
      <td><button onclick="deleteUser(${r.id})"
           class="btn btn-sm btn-danger">
           Delete</button></td>
    </tr>`).join('');
  renderPagination(total, page);
}

// 3. Pagination UI (~20 more lines)
// 4. Sort click handlers (~15 more lines)
// 5. Search with debounce (~12 more lines)
// 6. Delete + confirm modal (~25 more lines)
// 7. Skeleton loading state (~10 more lines)
// 8. Error + empty state handling (~8 more lines)
// ─────────────────────────────────────
// Total: ~95 lines + HTML boilerplate
// ...and you repeat ALL of it every table.
The real cost: Every new table is a full re-implementation. Sorting, search, infinite scroll, delete confirmations — all custom-wired every single time. One bug and you're auditing 95 lines.
After — SmartComponents

1 tag. Sort, search, pagination, delete — all included.

<!-- That's it. Seriously. -->
<smart-table
  api-url="/api/users/"
  response-map='{"dataPath":"results",
              "totalPath":"count"}'
  columns='[
    {"field":"name",   "label":"Name"},
    {"field":"email",  "label":"Email"},
    {"field":"status", "label":"Status",
     "type":"badge"}
  ]'
  delete-api-url="/api/users"
  page-size="10"
></smart-table>

<!-- Scroll mode auto-detected:
  ≤ page-size  → client-side rendering
  ≤ 1000 rows  → numbered pagination
  > 1000 rows  → infinite scroll -->
Free with every tag: Shimmer skeleton on load · click-to-sort headers · live search with debounce · auto scroll mode · delete confirm modal + row fade-out + toast · empty + error states · responsive overflow.
Before — The usual way

~67 lines per image type — and it still doesn't have a lightbox

<!-- HTML: skeleton placeholder -->
<div class="skeleton shimmer"
  id="img-placeholder"
  style="width:300px;height:200px;"></div>

// JS: set up IntersectionObserver
const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (!entry.isIntersecting) return;
    const img = new Image();
    img.src = entry.target.dataset.src;
    img.onload  = () => swapIn(entry.target, img);
    img.onerror = () => tryFallback(entry.target);
    observer.unobserve(entry.target);
  });
}, { rootMargin: '100px', threshold: 0.1 });
observer.observe(document.getElementById('img-placeholder'));

// Swap placeholder with fade-in (~10 lines)
function swapIn(el, img) { ... }

// Fallback if image fails (~10 lines)
function tryFallback(el) { ... }

// CSS: @keyframes shimmer sweep (~8 lines)
// CSS: hover-zoom on :hover (~5 lines)
// Click-to-preview lightbox (~20 lines)
// ─────────────────────────────────────
// Total: ~67 lines per "image type"
// Copy-pasted for every page.
The real cost: This gets copy-pasted for every page that needs lazy images. Want a spinner instead of shimmer? Refactor. Circle avatar? Fork the whole thing. Caption? More HTML. Lightbox? Another 20 lines.
After — SmartComponents

Every feature is just an attribute. No JS ever.

<!-- Basic: lazy + shimmer auto-enabled -->
<smart-image
  src="/media/photo.jpg"
  width="300" height="200"
  rounded
></smart-image>

<!-- Full-featured: everything in one tag -->
<smart-image
  src="/media/photo.jpg"
  fallback-src="/media/placeholder.jpg"
  width="300" height="200"
  rounded hover-zoom click-preview
  caption="Photo from the event"
></smart-image>

<!-- Circle avatar with spinner skeleton -->
<smart-image
  src="/media/avatar.jpg"
  width="64" height="64"
  animation-type="spinner" circle
></smart-image>

<!-- Fluid 16:9 banner -->
<smart-image src="/media/banner.jpg"
  aspect-ratio="16/9"
  style="width:100%" rounded
></smart-image>
Free with every tag: IntersectionObserver lazy load · shimmer or spinner skeleton · smooth fade-in · fallback-src on error with Retry button · hover zoom · click-to-preview lightbox · aspect-ratio fluid sizing · rounded / circle / square.
Before — The usual way

~52 lines per field — multiply by every form, every page

<!-- HTML: label + input + error element -->
<div class="form-group">
  <label for="email">Email
    <span class="required">*</span></label>
  <input type="email" id="email"
    class="form-control"
    placeholder="you@example.com">
  <span class="error-msg d-none"
    style="color:#dc3545;font-size:.8rem"></span>
</div>

// JS: attach blur validation
const input = document.getElementById('email');
const errEl = input.nextElementSibling;
input.addEventListener('blur', () => {
  if (!input.value.trim()) {
    showError('Email is required');
  } else if (!/\S+@\S+\.\S+/.test(input.value)) {
    showError('Enter a valid email address');
  } else { clearError(); }
});
function showError(msg) {
  input.classList.add('is-invalid');
  errEl.textContent = msg;
  errEl.classList.remove('d-none');
  input.style.animation = 'shake .3s';
  setTimeout(() => input.style.animation = '', 300);
}
// clearError() — ~5 lines
// @keyframes shake — ~8 CSS lines
// ×6–8 inputs per form = 300–400 lines total
The real cost: Every input gets its own HTML block, JS listener, and error span. A form with 8 fields = 400 lines of near-identical code. Change the error style once, touch 8 files.
After — SmartComponents

8 input types. One API. Zero boilerplate.

<!-- Text with required validation -->
<smart-input type="text"
  name="full_name" label="Full Name"
  placeholder="Enter your name"
  required
></smart-input>

<!-- Email auto-validates format -->
<smart-input type="email"
  name="email" label="Email"
  placeholder="you@example.com"
  required
></smart-input>

<!-- Select with options array -->
<smart-input type="select"
  name="role" label="Role"
  data-options='[{"id":"admin","name":"Admin"},
  {"id":"user","name":"User"}]'
></smart-input>

<!-- Switch toggle -->
<smart-input type="switch"
  name="notifications"
  label="Enable notifications"
></smart-input>
Free with every tag: Inline error messages · shake animation on invalid · email/required/pattern validation · 8 types from one API · FormData-compatible · data-oninput / data-onchange event hooks.
Before — The usual way

~45 lines per AJAX button — and there are 3–5 per page

<!-- HTML -->
<button id="saveBtn"
  class="btn btn-success">
  Save Changes
</button>

// JS: wire up everything manually
document.getElementById('saveBtn')
  .addEventListener('click', async e => {
    e.preventDefault();
    if (!confirm('Are you sure?')) return;

    btn.disabled = true;
    btn.innerHTML = `<span class="spinner-border
      spinner-border-sm"></span> Saving...`;

    try {
      const data = new FormData(
        document.getElementById('myForm'));
      const res = await fetch('/api/save',
        { method: 'POST', body: data });
      if (!res.ok) throw new Error();
      showToast('Saved!', 'success');
    } catch {
      showToast('Something went wrong', 'error');
    } finally {
      btn.innerHTML = 'Save Changes';
      btn.disabled = false;
    }
  });
// showToast() helper — another ~15 lines
// ×4 buttons per page = 180 lines of boilerplate
The real cost: 4 buttons × 45 lines = 180 lines of near-identical code per page. The confirm uses native confirm() which you can't style. Toast helpers live in a global utility file everyone forgets to import.
After — SmartComponents

Every button behaviour is a single attribute.

<!-- AJAX save with spinner + toast -->
<custom-button
  label="Save Changes"
  form-id="myForm"
  post="/api/save"
  buttontype="success"
  showspinner="true"
></custom-button>

<!-- Delete with styled confirm modal -->
<custom-button
  label="Delete Account"
  post="/api/users/42/delete"
  buttontype="danger"
  confirm-title="Delete this account?"
  confirm-message="This cannot be undone."
></custom-button>

<!-- Icon-only compact variant -->
<icon-button
  icon="pencil"
  post="/api/edit/42"
  tooltip="Edit record"
></icon-button>
Free with every tag: Auto-icon detection from label · spinner during request · Bootstrap confirm modal · success/error toast · AJAX POST with FormData · auto-disabled + re-enabled on completion.

Built for the problems
that actually waste your time.

Not theoretical best practices. Actual patterns developers re-implement on every single project, every single time.

🔁

Stop copy-pasting fetch boilerplate

async fetchData(), renderRows(), renderPagination(), handleSort()…
→ api-url + response-map. That's the whole table.

📐

Stop hand-rolling validation logic

addEventListener blur, regex test, show/hide error span, shake animation CSS…
→ required attribute. Everything else is automatic.

🖼️

Stop writing IntersectionObserver from scratch

observer.observe(), placeholder swap, skeleton CSS, fade-in, onerror fallback…
→ src + fallback-src. Lazy, shimmer, fade — zero config.

Infinite scroll vs pagination? Auto.

SmartTable reads your total record count and picks the right mode automatically — client-side, numbered pages, or infinite scroll — with zero configuration.

Delete row + confirm + toast? One attribute.

Add delete-api-url to any table. You get a styled confirmation modal, the DELETE request, row fade-out animation, and a success toast from a single attribute.

No build step. Works anywhere.

Drop a <script> tag. Write HTML. Django, Flask, Rails, plain files — SmartComponents doesn't care about your backend. It just works.

Try it now.

A live form built entirely with SmartComponents. Interact with it. Break it. Validate it.

<smart-input type="text"       name="full_name" label="Full Name"     required></smart-input>
<smart-input type="email"      name="email"     label="Email"          required></smart-input>
<smart-input type="select"
  name="status" label="Status"
  data-options='[{"id":"active","name":"Active"}, ...]'
></smart-input>
<smart-input type="datepicker" name="birthdate" label="Birth Date"></smart-input>
<smart-input type="textarea"   name="message"   label="Message" rows="3"></smart-input>
<smart-input type="switch"     name="notif"     label="Enable Notifications"></smart-input>
<custom-button
  label="Submit Form"
  form-id="hero-demo-form"
  post="/submit"
></custom-button>

Powerful primitives.

Each component is purpose-built, fully standalone, and works seamlessly together.

Username
Enter username...
<smart-input>

SmartInput

Universal input — text, email, password, select, date, file, checkbox, switch, textarea, radio. With validation and events.

View Docs →
💾 Save
🗑 Delete
<custom-button>

CustomSubmitButton

AJAX-powered button with automatic icons, loading states, confirmation dialogs, and form integration.

View Docs →
🔍 Search users
Alice Bob
<smart-search-input>

SmartSearchInput

Advanced search with async loading, multi-select badges, debounce, and keyboard navigation.

View Docs →
Name ⇅ Status Email
Alice active a@… 🗑
Bob pending b@… 🗑
<smart-table>

SmartTable

Replaces 95 lines of fetch + render + sort + paginate JS. Auto-detects scroll mode. Delete rows with one attribute.

View Docs →
shimmer spinner
<smart-image>

SmartImage

Lazy load, shimmer/spinner skeleton, fallback on error, hover zoom, click-to-preview lightbox — all from attributes.

View Docs →
📁
Dashboard
Admin
Settings
Configure
<smart-list-tile>

SmartListTile

Interactive list tile with leading icons, trailing icons, active states, ripple effect, and AJAX-powered actions.

View Docs →
B I U
Rich text content...
<smart-quill>

SmartQuill

Rich text editor wrapping Quill.js with built-in validation, form integration, and custom toolbar.

View Docs →
🚀 Launch
auto-icon · loading · ripple
<icon-button>

IconButton

Versatile icon button with ripple effect, AJAX calls, loading state, toast notifications, and auto icon detection.

View Docs →
Status
All ▾
Email
Filter…
Apply Reset
<smart-filter-bar>

SmartFilterBar

Declarative filter bar with text, date, and select inputs. Drives any <smart-table> via window events — no direct coupling.

View Docs →
File saved successfully.
New version available.
Unsaved changes.
<smart-toast>

SmartToast

Stacked, auto-dismissing toasts in 5 types. Fire from anywhere with one dispatchEvent() call. Supports promise mode.

View Docs →
full-page · scoped · flicker-safe
<smart-loader>

SmartLoader

Full-page or element-scoped overlay loader with blur backdrop, 200ms flicker prevention, and concurrent-safe show/hide.

View Docs →
🗑
Delete Item?
This action cannot be undone.
Cancel
Delete
<smart-modal>

SmartModal

Branded confirmation modal with custom title, message, and labels. Integrates with SmartTable deletes — replaces window.confirm().

View Docs →
Full Name *
Alice Smith
Role
Admin ▾
Save
AJAX · CSRF · validation · toast
<smart-form>

SmartForm

Declarative AJAX form engine. Wraps SmartInput, SmartSearchInput, and SmartQuill — collects values, validates, handles CSRF, maps field errors, and refreshes tables automatically.

View Docs →
overlay panel fade slide
<smart-motion>

SmartMotion

Barba.js page transition engine. 5 transition types — overlay, fade, slide, scale, panel. Re-executes page scripts, swaps head styles, and fires lifecycle events after every navigation.

View Docs →
scroll hover 8 presets
<smart-effects>

SmartEffects

Anime.js animation engine. 8 built-in presets, 5 trigger modes (page · scroll · hover · click · manual), custom attribute-driven animations, and auto mode for zero-config entrance effects.

View Docs →
Revenue ↑ +12.4%
WS live zoom export
<smart-chart>

SmartChart

Full-featured charting on Chart.js + ApexCharts. API fetch, SmartData source, inline, or WebSocket live. 10+ chart types, 6 palettes, date range filter, drag-to-zoom, goal lines, fullscreen, and CSV/PNG/JSON export.

View Docs →
if="user.role === 'admin'"
mode="remove"
<fallback> replace
if="" · <smart-permission>

SmartPermission

Reactive UI control system. Add if="" to any element — it hides, removes, disables, or shows a fallback based on live smartState. Supports lazy rendering, enter/leave animations, and compound JS expressions.

View Docs →
chart · span 2
kpi
24k
table · span 3
drag resize persist
<smart-grid>

SmartGrid

Declarative CSS Grid layout engine for dashboards. Responsive auto-fit columns, column and row spans, 5 breakpoints, drag-to-reorder, resize handles, masonry mode, and localStorage persistence.

View Docs →