@smilodon/core
v1.4.12
Published
High-performance native select component with extreme-scale virtualization - React, Vue, Svelte, Vanilla JS
Downloads
1,905
Maintainers
Readme
@smilodon/core
📖 Documentation
For comprehensive documentation covering all features, styling options, and advanced patterns:
Additional references:
- Performance Runbook:
docs/PERFORMANCE-RUNBOOK.md - Performance Guide:
docs/PERFORMANCE.md - Known Limitations:
docs/KNOWN-LIMITATIONS.md
The complete guide includes:
- ✅ All 60+ CSS variables for complete customization
- ✅ Vanilla JavaScript patterns (DOM manipulation, event listeners)
- ✅ Complete API reference with all properties and methods
- ✅ CDN usage and module bundler integration
- ✅ Custom renderers with HTML templates
- ✅ Theme examples and dynamic styling
- ✅ Advanced patterns (async loading, local storage, dependent selects)
- ✅ Troubleshooting and accessibility information
WebKit e2e note (Linux/Arch): WebKit Playwright binaries depend on older system libraries; on Arch-based distros we recommend running WebKit tests via the Playwright Docker image.
Why Smilodon?
Smilodon is a Web Component that renders 1,000,000+ items at 60 FPS with constant DOM size, sub-millisecond search, and zero framework lock-in. Built for extreme-scale data applications where legacy libraries crash or lag.
Performance Comparison
| Library | 10K Items | 100K Items | 1M Items | Memory | FPS | |---------|-----------|------------|----------|---------|-----| | Smilodon | 38ms | 81ms | 162ms | 18 MB | 60 | | React Select | 1200ms | ❌ Crash | ❌ Crash | 200+ MB | 10-25 | | Vue Select | 890ms | ❌ Crash | ❌ Crash | 180+ MB | 15-30 | | ng-select | 1100ms | ❌ Crash | ❌ Crash | 220+ MB | 12-28 |
See full benchmarks for methodology and reproducibility.
Installation
npm install @smilodon/coreQuick Start
Vanilla JavaScript / Web Components
<!DOCTYPE html>
<html>
<head>
<script type="module">
import '@smilodon/core';
</script>
</head>
<body>
<smilodon-select id="my-select"></smilodon-select>
<script type="module">
const select = document.getElementById('my-select');
// Generate 1 million items
const items = Array.from({ length: 1_000_000 }, (_, i) => ({
label: `Item ${i + 1}`,
value: i + 1
}));
select.items = items;
select.addEventListener('change', (e) => {
console.log('Selected:', e.detail);
});
</script>
</body>
</html>Framework Usage
@smilodon/core works directly in all frameworks as a Web Component - no adapters needed!
Simply import and use <smilodon-select> in React, Vue, Svelte, or any framework:
// React, Vue, Svelte - all work the same way
import '@smilodon/core';
<smilodon-select ref={selectRef} />Optional Framework Adapters (for enhanced developer experience):
- React:
npm install @smilodon/react- React hooks and components - Vue:
npm install @smilodon/vue- Vue composables and components - Svelte:
npm install @smilodon/svelte- Svelte stores and components - Vanilla:
npm install @smilodon/vanilla- Vanilla JS helpers
These adapters provide framework-native APIs (hooks, composables) for enhanced developer experience, but are not required - the core package works everywhere!
Note: Angular support has been discontinued as of December 2025.
See the main documentation for framework-specific examples.
Key Features
🚀 Extreme Performance
- Constant DOM: Only 10-15 elements rendered regardless of dataset size
- Work Stealing: Background search workers with automatic cancellation
- Sub-millisecond Search: Fenwick tree indexing for O(log n) queries
- 60 FPS Scrolling: Hardware-accelerated virtualization
🎯 Production Ready
- TypeScript First: Complete type definitions included
- Zero Dependencies: 6.6 KB gzipped runtime
- Framework Agnostic: Works with React, Vue, Svelte, or vanilla JS
- Accessibility: WCAG 2.2 AA compliant with ARIA 1.2
🔒 Enterprise Grade
- SOC2 Compliant: Audit-ready security controls
- CSP Compatible: No eval(), no inline scripts
- SBOM Included: Full dependency transparency
- 99.8% Test Coverage: Unit, integration, and E2E tests
API Reference
Clear Control (Selection/Search Reset)
const select = document.querySelector('enhanced-select');
select.updateConfig({
clearControl: {
enabled: true,
clearSelection: true,
clearSearch: true,
hideWhenEmpty: true,
ariaLabel: 'Clear selected and searched values',
icon: '✕',
}
});
select.addEventListener('clear', (e) => {
console.log(e.detail); // { clearedSelection: boolean, clearedSearch: boolean }
});Style hooks:
- Parts:
::part(clear-button),::part(clear-icon) - Tokens:
--select-clear-button-*,--select-clear-icon-*,--select-input-padding-with-clear
Properties
interface SmilodonSelectElement extends HTMLElement {
items: SelectItem[] | string[] | number[]; // Dataset (can be millions of items)
value: any; // Current selected value
placeholder?: string; // Placeholder text
searchable?: boolean; // Enable search (default: true)
disabled?: boolean; // Disable the select
multiple?: boolean; // Multiple selection mode
virtualization?: boolean; // Enable virtualization (default: true)
maxHeight?: number; // Max dropdown height in pixels
}
interface SelectItem {
label: string; // Display text
value: any; // Value (can be any type)
disabled?: boolean; // Disable this option
group?: string; // Optgroup name
}Flexible Input Formats:
- Object arrays:
[{ value: '1', label: 'Option 1' }, ...]- String arrays:
['Apple', 'Banana', 'Cherry']- automatically converted toSelectItemformat- Number arrays:
[1, 2, 3, 5, 8]- automatically converted toSelectItemformat
Examples with Different Input Types
Object Array (Traditional)
select.items = [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
{ value: 'cherry', label: 'Cherry' }
];String Array (Auto-converted)
select.items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
// Automatically becomes:
// [
// { value: 'Apple', label: 'Apple' },
// { value: 'Banana', label: 'Banana' },
// ...
// ]Number Array (Auto-converted)
select.items = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89];
// Automatically becomes:
// [
// { value: 1, label: '1' },
// { value: 2, label: '2' },
// ...
// ]🎯 Two Ways to Specify Options
Smilodon provides two powerful approaches for defining select options, each optimized for different use cases:
Method 1: Data-Driven (Object Arrays) 📊
Use when: You have structured data and want simple, declarative option rendering.
Advantages:
- ✅ Simple and declarative
- ✅ Auto-conversion from strings/numbers
- ✅ Perfect for basic dropdowns
- ✅ Zero boilerplate code
- ✅ Extremely performant (millions of items)
- ✅ Built-in search and filtering
- ✅ TypeScript type safety
Examples:
// Simple object array
const select = document.querySelector('enhanced-select');
select.items = [
{ value: '1', label: 'Apple' },
{ value: '2', label: 'Banana' },
{ value: '3', label: 'Cherry' }
];
// With additional metadata
select.items = [
{ value: 'us', label: 'United States', disabled: false },
{ value: 'ca', label: 'Canada', disabled: false },
{ value: 'mx', label: 'Mexico', disabled: true } // Disabled option
];
// With grouping
select.items = [
{ value: 'apple', label: 'Apple', group: 'Fruits' },
{ value: 'banana', label: 'Banana', group: 'Fruits' },
{ value: 'carrot', label: 'Carrot', group: 'Vegetables' },
{ value: 'broccoli', label: 'Broccoli', group: 'Vegetables' }
];
// Auto-conversion from strings
select.items = ['Red', 'Green', 'Blue', 'Yellow'];
// Auto-conversion from numbers
select.items = [10, 20, 30, 40, 50];
// Large datasets (millions of items)
select.items = Array.from({ length: 1_000_000 }, (_, i) => ({
value: i,
label: `Item ${i + 1}`
}));Method 2: Component-Driven (Custom Renderers) 🎨
Use when: You need rich, interactive option content with custom HTML/styling.
Advantages:
- ✅ Full control over option rendering
- ✅ Rich content (images, icons, badges, multi-line text)
- ✅ Custom HTML and styling
- ✅ Interactive elements within options
- ✅ Conditional rendering based on item data
- ✅ Perfect for complex UIs (user cards, product listings, etc.)
How it works: Provide an optionTemplate function that returns HTML string for each option.
Examples:
const select = document.querySelector('enhanced-select');
// Example 1: Simple custom template with icons
const items = [
{ value: 'js', label: 'JavaScript', icon: '🟨', description: 'Dynamic scripting language' },
{ value: 'py', label: 'Python', icon: '🐍', description: 'General-purpose programming' },
{ value: 'rs', label: 'Rust', icon: '🦀', description: 'Systems programming language' }
];
select.items = items;
select.optionTemplate = (item, index) => `
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 24px;">${item.icon}</span>
<div>
<div style="font-weight: 600;">${item.label}</div>
<div style="font-size: 12px; color: #6b7280;">${item.description}</div>
</div>
</div>
`;
// Example 2: User selection with avatars
const users = [
{
value: '1',
label: 'John Doe',
email: '[email protected]',
avatar: 'https://i.pravatar.cc/150?img=1',
role: 'Admin'
},
{
value: '2',
label: 'Jane Smith',
email: '[email protected]',
avatar: 'https://i.pravatar.cc/150?img=2',
role: 'User'
},
{
value: '3',
label: 'Bob Johnson',
email: '[email protected]',
avatar: 'https://i.pravatar.cc/150?img=3',
role: 'Moderator'
}
];
select.items = users;
select.optionTemplate = (item, index) => `
<div style="display: flex; align-items: center; gap: 12px; padding: 4px 0;">
<img
src="${item.avatar}"
alt="${item.label}"
style="width: 40px; height: 40px; border-radius: 50%; object-fit: cover;"
/>
<div style="flex: 1;">
<div style="font-weight: 600; color: #1f2937;">${item.label}</div>
<div style="font-size: 13px; color: #6b7280;">${item.email}</div>
</div>
<span style="
padding: 4px 8px;
background: ${item.role === 'Admin' ? '#dbeafe' : '#f3f4f6'};
color: ${item.role === 'Admin' ? '#1e40af' : '#374151'};
border-radius: 12px;
font-size: 11px;
font-weight: 600;
">${item.role}</span>
</div>
`;
// Example 3: Product selection with images and pricing
const products = [
{
value: 'p1',
label: 'Premium Laptop',
price: 1299.99,
stock: 15,
image: 'https://via.placeholder.com/60',
badge: 'Best Seller'
},
{
value: 'p2',
label: 'Wireless Mouse',
price: 29.99,
stock: 150,
image: 'https://via.placeholder.com/60',
badge: null
},
{
value: 'p3',
label: 'Mechanical Keyboard',
price: 89.99,
stock: 0,
image: 'https://via.placeholder.com/60',
badge: 'Out of Stock'
}
];
select.items = products;
select.optionTemplate = (item, index) => `
<div style="display: flex; align-items: center; gap: 12px; opacity: ${item.stock === 0 ? '0.5' : '1'};">
<img
src="${item.image}"
alt="${item.label}"
style="width: 60px; height: 60px; border-radius: 8px; object-fit: cover; border: 1px solid #e5e7eb;"
/>
<div style="flex: 1;">
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-weight: 600; color: #1f2937;">${item.label}</span>
${item.badge ? `
<span style="
padding: 2px 6px;
background: ${item.badge === 'Best Seller' ? '#dcfce7' : '#fee2e2'};
color: ${item.badge === 'Best Seller' ? '#166534' : '#991b1b'};
border-radius: 4px;
font-size: 10px;
font-weight: 600;
">${item.badge}</span>
` : ''}
</div>
<div style="margin-top: 4px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 16px; font-weight: 700; color: #059669;">$${item.price.toFixed(2)}</span>
<span style="font-size: 12px; color: #6b7280;">${item.stock > 0 ? `${item.stock} in stock` : 'Out of stock'}</span>
</div>
</div>
</div>
`;
// Example 4: Status indicators with conditional styling
const tasks = [
{ value: 't1', label: 'Design Homepage', status: 'completed', priority: 'high', assignee: 'John' },
{ value: 't2', label: 'API Integration', status: 'in-progress', priority: 'high', assignee: 'Jane' },
{ value: 't3', label: 'Write Documentation', status: 'pending', priority: 'medium', assignee: 'Bob' },
{ value: 't4', label: 'Bug Fixes', status: 'in-progress', priority: 'low', assignee: 'Alice' }
];
const statusColors = {
'completed': { bg: '#dcfce7', color: '#166534', icon: '✓' },
'in-progress': { bg: '#dbeafe', color: '#1e40af', icon: '⟳' },
'pending': { bg: '#fef3c7', color: '#92400e', icon: '○' }
};
const priorityColors = {
'high': '#ef4444',
'medium': '#f59e0b',
'low': '#10b981'
};
select.items = tasks;
select.optionTemplate = (item, index) => {
const status = statusColors[item.status];
return `
<div style="display: flex; align-items: center; gap: 10px; padding: 4px 0;">
<div style="
width: 24px;
height: 24px;
border-radius: 50%;
background: ${status.bg};
color: ${status.color};
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
">${status.icon}</div>
<div style="flex: 1;">
<div style="font-weight: 600; color: #1f2937;">${item.label}</div>
<div style="font-size: 12px; color: #6b7280; margin-top: 2px;">
Assigned to ${item.assignee}
</div>
</div>
<div style="
width: 8px;
height: 8px;
border-radius: 50%;
background: ${priorityColors[item.priority]};
" title="${item.priority} priority"></div>
</div>
`;
};Comparison: When to Use Each Method
| Feature | Method 1: Object Arrays | Method 2: Custom Renderers | |---------|------------------------|---------------------------| | Setup Complexity | ⭐ Simple | ⭐⭐ Moderate | | Rendering Speed | ⭐⭐⭐ Fastest | ⭐⭐ Fast | | Visual Customization | ⭐⭐ Limited | ⭐⭐⭐ Unlimited | | Use Case | Standard dropdowns | Rich, complex UIs | | Code Amount | Minimal | More code | | TypeScript Support | ⭐⭐⭐ Full | ⭐⭐⭐ Full | | Performance (1M items) | ⭐⭐⭐ Excellent | ⭐⭐ Good | | Learning Curve | ⭐ Easy | ⭐⭐ Medium |
Best Practices:
✅ Use Method 1 (Object Arrays) when:
- You need simple text-based options
- Performance is critical (millions of items)
- You want minimal code
- Built-in search/filter is sufficient
✅ Use Method 2 (Custom Renderers) when:
- You need images, icons, or badges
- Options require multiple lines of text
- Custom styling/layout is important
- Conditional rendering based on data
- Rich user experience is priority
Combining Both Methods
You can start with Method 1 and add Method 2 later as your UI evolves:
// Start simple
select.items = ['Option 1', 'Option 2', 'Option 3'];
// Later, add custom rendering without changing items
select.optionTemplate = (item, index) => `
<div style="padding: 8px; background: ${index % 2 ? '#f9fafb' : 'white'};">
<strong>${item.label || item}</strong>
</div>
`;Events
// Selection changed
select.addEventListener('change', (event: CustomEvent) => {
console.log(event.detail); // { value, label }
});
// Dropdown opened
select.addEventListener('open', () => {
console.log('Dropdown opened');
});
// Dropdown closed
select.addEventListener('close', () => {
console.log('Dropdown closed');
});
// Search query changed
select.addEventListener('search', (event: CustomEvent) => {
console.log(event.detail.query);
});Methods
// Programmatically open/close
select.open();
select.close();
// Clear selection
select.clear();
// Focus the select
select.focus();
// Get filtered items (useful for debugging)
const filtered = select.getFilteredItems();Advanced Usage
Custom Styling
Smilodon uses CSS custom properties (CSS variables) for easy theming and customization. The default theme is light mode with a clean white background.
Basic Customization
enhanced-select {
/* Options styling */
--select-option-bg: #ffffff;
--select-option-color: #1f2937;
--select-option-padding: 8px 12px;
/* Hover state */
--select-option-hover-bg: #f3f4f6;
--select-option-hover-color: #1f2937;
/* Selected state */
--select-option-selected-bg: #e0e7ff;
--select-option-selected-color: #4338ca;
--select-option-selected-border: 1px solid #4338ca;
--select-option-selected-hover-bg: #c7d2fe;
--select-option-selected-hover-border: 1px solid #3730a3;
/* Active/focused state */
--select-option-active-bg: #f3f4f6;
--select-option-active-color: #1f2937;
/* Dropdown */
--select-dropdown-bg: white;
--select-dropdown-border: #ccc;
--select-dropdown-shadow: 0 4px 6px rgba(0,0,0,0.1);
}Dark Mode (Opt-in)
Dark mode is opt-in only and can be enabled by adding a class or data attribute:
<!-- Using class -->
<enhanced-select class="dark-mode"></enhanced-select>
<!-- Using data attribute -->
<enhanced-select data-theme="dark"></enhanced-select>
<!-- Also supported (attribute aliases) -->
<enhanced-select dark-mode></enhanced-select>
<enhanced-select darkmode></enhanced-select>
<enhanced-select theme="dark"></enhanced-select>/* Custom dark mode colors */
enhanced-select.dark-mode {
--select-dark-bg: #1f2937;
--select-dark-text: #f9fafb;
--select-dark-border: #4b5563;
--select-dark-dropdown-bg: #1f2937;
--select-dark-option-color: #f9fafb;
--select-dark-option-bg: #1f2937;
--select-dark-option-hover-bg: #374151;
--select-dark-option-selected-bg: #3730a3;
}Available CSS Variables
Light Mode (Default)
--select-options-bg /* Options container background (white) */
--select-width /* Host width (100% default) */
--select-height /* Host height (auto default) */
--select-option-color /* Option text color (#1f2937) */
--select-option-bg /* Option background (white) */
--select-option-padding /* Option padding (8px 12px) */
--select-option-hover-bg /* Hover background (#f3f4f6) */
--select-option-hover-color /* Hover text color (#1f2937) */
--select-option-selected-bg /* Selected background (#e0e7ff) */
--select-option-selected-color /* Selected text color (#4338ca) */
--select-option-selected-border /* Selected border (inherits option border by default) */
--select-option-selected-hover-bg /* Selected+hover background (inherits selected bg by default) */
--select-option-selected-hover-border /* Selected+hover border (inherits selected border by default) */
--select-option-active-bg /* Active background (#f3f4f6) */
--select-option-active-color /* Active text color (#1f2937) */
--select-input-width /* Input field width */
--select-input-height /* Input container height */
--select-dropdown-bg /* Dropdown background (white) */
--select-dropdown-border /* Dropdown border color (#ccc) */
--select-dropdown-shadow /* Dropdown shadow */
--select-empty-padding /* Empty/no-results container padding */
--select-empty-color /* Text color for empty/no-results models */
--select-empty-font-size /* Font size */
--select-empty-bg /* Background for empty/no-results state */
--select-empty-min-height /* Minimum height of empty state box */
/* Arrow/button */
--select-arrow-size /* Width & height of SVG icon (16px default) */
--select-arrow-color /* Icon color (#667eea) */
--select-arrow-hover-color /* Icon color when hovered (#667eea) */
--select-arrow-hover-bg /* Background when hover (rgba(102,126,234,0.08)) */
--select-arrow-width /* Container width (40px) */
--select-arrow-border-radius /* Container border radius */
/* Group headers (when using groupedItems or flat items with `group` property) */
--select-group-header-padding /* Padding inside header (8px 12px) */
--select-group-header-color /* Text color (#6b7280) */
--select-group-header-bg /* Background (#f3f4f6) */
--select-group-header-font-size
--select-group-header-text-align /* Header text alignment (left default) */
--select-group-header-text-transform
--select-group-header-letter-spacing
--select-group-header-border-bottom
/* Option content and checkmark hooks */
--select-option-content-overflow
--select-option-content-text-overflow
--select-option-content-white-space
--select-checkmark-margin-left
--select-checkmark-colorDark Mode (Opt-in)
--select-dark-bg /* Dark input background (#1f2937) */
--select-dark-text /* Dark text color (#f9fafb) */
--select-dark-border /* Dark border color (#4b5563) */
--select-dark-dropdown-bg /* Dark dropdown background (#1f2937) */
--select-dark-options-bg /* Dark options container bg (#1f2937) */
--select-dark-option-color /* Dark option text (#f9fafb) */
--select-dark-option-bg /* Dark option background (#1f2937) */
--select-dark-option-hover-bg /* Dark hover background (#374151) */
--select-dark-option-selected-bg /* Dark selected bg (#3730a3) */
--select-dark-group-header-color /* Dark header text */
--select-dark-group-header-bg /* Dark header background */Complete CSS Variables List (60+ variables)
See the full CSS variables reference for all 60+ customizable properties including:
- Input container (gap, padding, height, borders, focus states)
- Input field (width, padding, font, colors)
- Arrow/icon (size, color, hover states, position)
- Separator line (width, height, gradient, position)
- Selection badges (padding, colors, remove/delete button)
- Dropdown (margins, max-height, borders, shadows)
- Options (font size, line height, borders, transitions)
- Load more button (padding, borders, colors, hover states)
- Loading/empty states (padding, colors, backgrounds, spinner)
Highlighted Customization Features
Separator Line Between Input and Arrow The vertical separator line that appears between the input area and the dropdown arrow is fully customizable:
enhanced-select {
/* Customize the separator line */
--select-separator-width: 2px; /* Line thickness */
--select-separator-height: 80%; /* Line height */
--select-separator-position: 40px; /* Distance from right edge */
--select-separator-gradient: linear-gradient(
to bottom,
transparent 0%,
#3b82f6 20%,
#3b82f6 80%,
transparent 100%
);
}
/* Typo-compatible aliases are also accepted */
enhanced-select {
--select-seperator-width: 2px;
--select-seperator-height: 80%;
--select-seperator-position: 40px;
--select-seperator-gradient: linear-gradient(to bottom, transparent 0%, #3b82f6 20%, #3b82f6 80%, transparent 100%);
}Gradient Dropdown + Hover/Selected States
If your dropdown uses a gradient background (for example via --select-dropdown-bg), option hover/selected colors still work as expected. The component intentionally uses the background shorthand for hover/selected option states so any inherited background-image layers are cleared correctly.
Badge Remove/Delete Button (Multi-Select) The × button that removes selected items in multi-select mode is fully customizable:
Group Header & No‑Results Parts
Both the group header and the no-results message are exposed as shadow parts (group-header and no-results) so you can target them with ::part() selectors or CSS variables. This makes it straightforward to match the look of your host framework or UI kit.
enhanced-select {
/* Customize badge appearance */
--select-badge-bg: #10b981; /* Badge background */
--select-badge-color: white; /* Badge text color */
--select-badge-padding: 6px 10px; /* Badge spacing */
/* Customize the × (remove/delete) button */
--select-badge-remove-size: 18px; /* Button size */
--select-badge-remove-bg: rgba(255, 255, 255, 0.3); /* Button background */
--select-badge-remove-color: white; /* × symbol color */
--select-badge-remove-font-size: 18px; /* × symbol size */
--select-badge-remove-hover-bg: rgba(255, 255, 255, 0.6); /* Hover state */
}Real-World Customization Examples
Example 1: Bootstrap-style Select
enhanced-select {
--select-input-border: 1px solid #ced4da;
--select-input-border-radius: 0.375rem;
--select-input-focus-border: #86b7fe;
--select-input-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--select-option-hover-bg: #e9ecef;
--select-option-selected-bg: #0d6efd;
--select-option-selected-color: white;
--select-badge-bg: #0d6efd;
}Example 2: Material Design
enhanced-select {
--select-input-border-radius: 4px;
--select-input-focus-border: #1976d2;
--select-input-focus-shadow: none;
--select-option-padding: 16px;
--select-option-hover-bg: rgba(0, 0, 0, 0.04);
--select-option-selected-bg: #e3f2fd;
--select-option-selected-color: #1976d2;
--select-badge-bg: #1976d2;
--select-badge-border-radius: 16px;
}Example 3: Tailwind-style
enhanced-select {
--select-input-border: 1px solid #e5e7eb;
--select-input-border-radius: 0.5rem;
--select-input-focus-border: #3b82f6;
--select-input-focus-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
--select-option-padding: 0.5rem 0.75rem;
--select-option-hover-bg: #f3f4f6;
--select-option-selected-bg: #dbeafe;
--select-option-selected-color: #1e40af;
}Example 4: Custom Brand Colors
enhanced-select {
/* Your brand colors */
--select-input-focus-border: #ff6b6b;
--select-arrow-color: #ff6b6b;
--select-badge-bg: #ff6b6b;
--select-option-selected-bg: #ffe0e0;
--select-option-selected-color: #c92a2a;
--select-button-border: 1px solid #ff6b6b;
--select-button-color: #ff6b6b;
--select-button-hover-bg: #ff6b6b;
}Framework-Specific Examples
React - Customizing Separator & Badge Remove Button
import { Select } from '@smilodon/react';
function App() {
return (
<Select
items={items}
multiple
style={{
'--select-option-hover-bg': '#2563eb',
'--select-option-padding': '12px 16px',
'--select-badge-bg': '#3b82f6',
'--select-badge-remove-bg': 'rgba(255, 255, 255, 0.4)',
'--select-badge-remove-hover-bg': 'rgba(255, 255, 255, 0.7)',
'--select-separator-gradient': 'linear-gradient(to bottom, transparent 0%, #3b82f6 20%, #3b82f6 80%, transparent 100%)'
}}
/>
);
}Vue - Complete Customization
<template>
<Select
:items="items"
multiple
:style="{
'--select-option-hover-bg': '#2563eb',
'--select-option-padding': '12px 16px',
'--select-badge-bg': '#3b82f6',
'--select-badge-remove-size': '20px',
'--select-badge-remove-bg': 'rgba(255, 255, 255, 0.4)',
'--select-separator-width': '2px',
'--select-separator-height': '70%'
}"
/>
</template>Svelte - Themed Components
<script>
import { Select } from '@smilodon/svelte';
</script>
<Select
items={items}
multiple
style="
--select-badge-bg: #10b981;
--select-badge-remove-bg: rgba(255, 255, 255, 0.3);
--select-badge-remove-hover-bg: rgba(255, 255, 255, 0.6);
--select-separator-gradient: linear-gradient(to bottom, transparent, #10b981, transparent);
"
/>Vanilla JS - Dynamic Styling
const select = document.querySelector('enhanced-select');
// Apply custom separator and badge styles
select.style.setProperty('--select-separator-width', '2px');
select.style.setProperty('--select-separator-gradient', 'linear-gradient(to bottom, transparent, #ff6b6b, transparent)');
select.style.setProperty('--select-badge-bg', '#ff6b6b');
select.style.setProperty('--select-badge-remove-size', '18px');
select.style.setProperty('--select-badge-remove-bg', 'rgba(255, 255, 255, 0.3)');Server-Side Rendering (SSR)
Smilodon gracefully handles SSR environments:
// Check if running in browser
if (typeof window !== 'undefined') {
import('@smilodon/core').then(() => {
// Initialize after hydration
});
}Performance Monitoring
Enable telemetry to monitor performance:
select.enableTelemetry = true;
select.addEventListener('telemetry', (event) => {
console.log('Performance metrics:', event.detail);
// { renderTime, searchTime, scrollFPS, memoryUsage }
});Architecture
Smilodon's performance comes from three core optimizations:
- Virtual Scrolling: Only renders visible items (10-15 DOM nodes for any dataset size)
- Work Stealing Scheduler: Search operations run in background workers with automatic cancellation
- Fenwick Tree Indexing: O(log n) search queries instead of O(n) array scans
Read the full architecture guide for implementation details.
Browser Support
| Browser | Version | |---------|---------| | Chrome | 90+ | | Firefox | 88+ | | Safari | 14.1+ | | Edge | 90+ |
Requires browsers with native Web Components support (custom elements v1).
Bundle Size
- Runtime: 6.6 KB gzipped (ESM)
- Full Package: 365 KB (includes 5 formats + source maps + types)
- Tree-shakeable: Import only what you need
TypeScript
Full TypeScript definitions included. No need for @types/* packages.
import type { SmilodonSelectElement, SelectItem } from '@smilodon/core';
const items: SelectItem[] = [
{ label: 'Option 1', value: 1 },
{ label: 'Option 2', value: 2 }
];
const select = document.querySelector('smilodon-select') as SmilodonSelectElement;
select.items = items;Documentation
- Main README: Complete guide with framework examples
- Benchmarks: Transparent performance comparisons
- API Reference: Full API documentation
- Architecture: Technical deep-dive
- Migration Guide: Migrate from React Select, Vue Select, etc.
Examples
Visit the interactive playground to see Smilodon in action with:
- 1M item datasets
- Real-time search
- Framework integrations
- Custom styling
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
MIT © Navid Rezadoost
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Security: Security Policy
