Tables. Images. Inputs. Buttons.
Everything you write 80 lines of fetch + render + validate JS for —
now a single attribute-driven HTML tag.
We counted. Every attribute replaces something you'd otherwise wire up by hand — every time, for every project.
See exactly what each SmartComponent replaces — line by line, feature by feature.
// 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.
<!-- 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 -->
<!-- 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.
<!-- 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>
<!-- 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
<!-- 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>
<!-- 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
confirm() which you can't style. Toast helpers live in a global utility file everyone forgets to import.
<!-- 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>
Not theoretical best practices. Actual patterns developers re-implement on every single project, every single time.
async fetchData(), renderRows(), renderPagination(), handleSort()…
→ api-url + response-map. That's the whole table.
addEventListener blur, regex test, show/hide error span, shake animation CSS…
→ required attribute. Everything else is automatic.
observer.observe(), placeholder swap, skeleton CSS, fade-in, onerror fallback…
→ src + fallback-src. Lazy, shimmer, fade — zero config.
SmartTable reads your total record count and picks the right mode automatically — client-side, numbered pages, or infinite scroll — with zero configuration.
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.
Drop a <script> tag. Write HTML. Django, Flask, Rails, plain files — SmartComponents doesn't care about your backend. It just works.
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>
Each component is purpose-built, fully standalone, and works seamlessly together.
Universal input — text, email, password, select, date, file, checkbox, switch, textarea, radio. With validation and events.
View Docs →AJAX-powered button with automatic icons, loading states, confirmation dialogs, and form integration.
View Docs →Advanced search with async loading, multi-select badges, debounce, and keyboard navigation.
View Docs →Replaces 95 lines of fetch + render + sort + paginate JS. Auto-detects scroll mode. Delete rows with one attribute.
View Docs →Lazy load, shimmer/spinner skeleton, fallback on error, hover zoom, click-to-preview lightbox — all from attributes.
View Docs →Interactive list tile with leading icons, trailing icons, active states, ripple effect, and AJAX-powered actions.
View Docs →Rich text editor wrapping Quill.js with built-in validation, form integration, and custom toolbar.
View Docs →Versatile icon button with ripple effect, AJAX calls, loading state, toast notifications, and auto icon detection.
View Docs →Declarative filter bar with text, date, and select inputs. Drives any <smart-table> via window events — no direct coupling.
Stacked, auto-dismissing toasts in 5 types. Fire from anywhere with one dispatchEvent() call. Supports promise mode.
Full-page or element-scoped overlay loader with blur backdrop, 200ms flicker prevention, and concurrent-safe show/hide.
View Docs →Branded confirmation modal with custom title, message, and labels. Integrates with SmartTable deletes — replaces window.confirm().
Declarative AJAX form engine. Wraps SmartInput, SmartSearchInput, and SmartQuill — collects values, validates, handles CSRF, maps field errors, and refreshes tables automatically.
View Docs →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 →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 →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 →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.
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.