npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smilodon/core

v1.4.12

Published

High-performance native select component with extreme-scale virtualization - React, Vue, Svelte, Vanilla JS

Downloads

1,905

Readme

@smilodon/core

📖 Documentation

For comprehensive documentation covering all features, styling options, and advanced patterns:

👉 Complete Vanilla JS Guide 👈

Additional references:

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/core

Quick 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 to SelectItem format
  • Number arrays: [1, 2, 3, 5, 8] - automatically converted to SelectItem format

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-color

Dark 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:

  1. Virtual Scrolling: Only renders visible items (10-15 DOM nodes for any dataset size)
  2. Work Stealing Scheduler: Search operations run in background workers with automatic cancellation
  3. 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

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