vanilla-smart-select
v1.0.1
Published
Modern JavaScript dropdown enhancement library without jQuery dependencies
Maintainers
Readme
🎯 Vanilla Smart Select
English | Português
Biblioteca moderna, completa, leve e poderosa de aprimoramento de dropdown escrita em vanilla JavaScript puro - sem necessidade de jQuery.
Transforme elementos nativos
<select>em dropdowns poderosos e pesquisáveis com suporte AJAX, templates customizados, internacionalização e muito mais - tudo sem dependências.
📑 Navegação Rápida
- Recursos • Instalação • Início Rápido
- Documentação • Referência da API • Eventos
- Suporte a Navegadores • Contribuindo
✨ O Que Você Ganha
Transforme este select básico:
<select>
<option>Opção 1</option>
<option>Opção 2</option>
</select>Em um dropdown poderoso com:
- 🔍 Busca ao Vivo - Encontre opções instantaneamente enquanto digita
- ⌨️ Navegação por Teclado - Setas, Enter, Escape, Tab
- 🎯 Multi-Seleção - Selecione múltiplos itens com tags
- 🌐 Carregamento AJAX - Carregue dados de APIs dinamicamente
- 📱 Mobile Friendly - Interface otimizada para toque
- ♿ Acessível - Compatível com leitores de tela, em conformidade com WCAG
- 🎨 Customizável - Controle total sobre estilos e templates
Tudo com apenas 3 linhas de JavaScript!
✨ Recursos
- 🚀 Zero Dependências - Vanilla JavaScript puro, sem necessidade de jQuery
- 📦 Leve - ~50KB minificado, ~15KB gzipped
- ♿ Acessível - Compatível com WCAG 2.1 Nível AA com suporte ARIA completo
- 🔍 Busca Inteligente - Busca em tempo real com suporte a diacríticos
- 🌍 Internacionalização - Suporte multi-idioma (EN, PT-BR, ES) com detecção automática
- 🎨 Templates Customizados - Controle total sobre renderização de itens com HTML/CSS
- 🔄 Suporte AJAX - Carregamento de dados remotos com debounce e cache
- 📜 Scroll Infinito - Paginação automática para grandes conjuntos de dados
- 🏷️ Tagging - Crie novas opções dinamicamente
- ⌨️ Navegação por Teclado - Acessibilidade completa por teclado
- 🎯 Multi-Seleção - Selecione múltiplos itens com limites opcionais
- 📊 Optgroups - Organize opções em grupos
- 🎨 Temas - Fácil de customizar com variáveis CSS
- 📱 Responsivo - Funciona perfeitamente em desktop e mobile
- 🅱️ Compatível com Bootstrap - Funciona perfeitamente com Bootstrap 5
🤔 Por Que Vanilla Smart Select?
Problema: Elementos nativos <select> são limitados e difíceis de estilizar. A maioria das bibliotecas de aprimoramento requer dependências pesadas como jQuery.
Solução: Vanilla Smart Select fornece funcionalidade poderosa de dropdown com:
- ✅ Zero Dependências - Vanilla JavaScript puro, sem jQuery ou outras bibliotecas
- ✅ Leve - Tamanho de bundle pequeno (~15KB gzipped) vs alternativas pesadas
- ✅ Arquitetura Moderna - Construído com ES6+ e padrões web modernos
- ✅ Melhor Performance - Renderização otimizada e cache inteligente
- ✅ Recursos Ricos - i18n, AJAX, tagging, templates customizados e muito mais
Perfeito para: Aplicações web modernas, projetos React/Vue/Angular, Progressive Web Apps e qualquer projeto que valorize performance e código limpo.
📦 Instalação
NPM
npm install vanilla-smart-selectYarn
yarn add vanilla-smart-selectCDN
<!-- CSS -->
<link rel="stylesheet" href="https://unpkg.com/vanilla-smart-select/dist/vanilla-smart-select.min.css">
<!-- JavaScript -->
<script src="https://unpkg.com/vanilla-smart-select/dist/vanilla-smart-select.min.js"></script>ES Modules
import VanillaSmartSelect from 'vanilla-smart-select';
import 'vanilla-smart-select/dist/vanilla-smart-select.min.css';🚀 Início Rápido
1. Inclua os arquivos
<!-- CSS -->
<link rel="stylesheet" href="node_modules/vanilla-smart-select/dist/vanilla-smart-select.min.css">
<!-- JavaScript -->
<script src="node_modules/vanilla-smart-select/dist/vanilla-smart-select.min.js"></script>2. Crie seu HTML
<select id="mySelect">
<option value="">Escolha uma opção...</option>
<option value="1">JavaScript</option>
<option value="2">Python</option>
<option value="3">Ruby</option>
<option value="4">Go</option>
</select>3. Inicialize
const select = new VanillaSmartSelect('#mySelect', {
searchable: true,
placeholder: 'Selecione uma linguagem...'
});É isso! Seu select agora está aprimorado com busca, navegação por teclado e melhor estilização.
💡 Exemplos de Uso Básico
Select Pesquisável Simples
<select id="countries">
<option value="">Selecione um país...</option>
<option value="us">Estados Unidos</option>
<option value="ca">Canadá</option>
<option value="br">Brasil</option>
</select>
<script>
new VanillaSmartSelect('#countries', {
searchable: true
});
</script>Com Array de Dados
const select = new VanillaSmartSelect('#mySelect', {
data: [
{ id: 1, text: 'JavaScript' },
{ id: 2, text: 'Python' },
{ id: 3, text: 'Ruby', disabled: true },
{
text: 'Frontend',
children: [
{ id: 4, text: 'React' },
{ id: 5, text: 'Vue' },
{ id: 6, text: 'Angular' }
]
}
],
placeholder: 'Selecione uma linguagem...'
});📖 Documentação
Índice
Recursos Principais
Seleção Única
Dropdown simples com busca e botão de limpar:
const select = new VanillaSmartSelect('#single-select', {
placeholder: 'Escolha uma opção...',
allowClear: true,
searchable: true
});Multi-Seleção
Selecione múltiplas opções com limite opcional de seleção:
const select = new VanillaSmartSelect('#multi-select', {
multiple: true,
placeholder: 'Selecione múltiplos itens...',
maximumSelectionLength: 5 // Limite opcional
});Busca e Filtragem
Busca poderosa com suporte a diacríticos e matchers customizados:
const select = new VanillaSmartSelect('#searchable-select', {
searchable: true,
searchMinimumLength: 2,
searchDelay: 250,
matchStrategy: 'contains', // 'startsWith' | 'contains' | 'exact'
// Matcher customizado
matcher: (params, item) => {
const term = params.term.toLowerCase();
const text = item.text.toLowerCase();
return text.includes(term);
}
});Optgroups
Organize opções em grupos:
const select = new VanillaSmartSelect('#grouped-select', {
data: [
{
text: 'Frutas',
children: [
{ id: 'apple', text: 'Maçã' },
{ id: 'banana', text: 'Banana' }
]
},
{
text: 'Vegetais',
children: [
{ id: 'carrot', text: 'Cenoura' },
{ id: 'lettuce', text: 'Alface' }
]
}
]
});Recursos Avançados
Internacionalização (i18n)
Suporte integrado para múltiplos idiomas com detecção automática:
import { getLanguage } from 'vanilla-smart-select/i18n';
// Detectar idioma do navegador automaticamente (EN, PT-BR ou ES)
const select = new VanillaSmartSelect('#i18n-select'); // Detecta automaticamente
// Ou definir manualmente
const select = new VanillaSmartSelect('#i18n-select', {
language: getLanguage('pt-BR')
});
// Alterar idioma dinamicamente
select.updateLanguage(getLanguage('es'));Idiomas Suportados:
- 🇺🇸 Inglês (en)
- 🇧🇷 Português - Brasil (pt-BR)
- 🇪🇸 Espanhol (es)
Mensagens Customizadas:
const select = new VanillaSmartSelect('#select', {
language: {
noResults: 'Nenhum resultado encontrado',
searching: 'Buscando...',
loading: 'Carregando...',
errorLoading: 'Erro ao carregar resultados',
inputTooShort: (args) => `Digite mais ${args.minimum} caracteres`,
maximumSelected: (args) => `Máximo de ${args.maximum} itens`
}
});Templates Customizados
Controle total sobre como os itens são renderizados:
const select = new VanillaSmartSelect('#custom-template', {
data: [
{
id: 1,
text: 'João Silva',
avatar: 'https://i.pravatar.cc/32?img=1',
email: '[email protected]'
}
],
// Customizar itens do dropdown
templateResult: (item) => {
if (item._isTag) {
return `<div style="color: #007bff;">➕ Criar: ${item.text}</div>`;
}
const div = document.createElement('div');
div.style.display = 'flex';
div.style.gap = '10px';
div.innerHTML = `
<img src="${item.avatar}" style="width: 32px; height: 32px; border-radius: 50%;">
<div>
<div style="font-weight: 500;">${item.text}</div>
<div style="font-size: 12px; color: #666;">${item.email}</div>
</div>
`;
return div;
},
// Customizar itens selecionados
templateSelection: (item) => {
return `<img src="${item.avatar}" style="width: 16px; border-radius: 50%;"> ${item.text}`;
}
});Funções de Template:
- templateResult - Customiza itens na lista dropdown
- templateSelection - Customiza exibição de itens selecionados
- Retorno:
HTMLElementoustring(HTML)
Carregamento AJAX
Carregue dados de fontes remotas com debouncing e cache:
const select = new VanillaSmartSelect('#ajax-select', {
placeholder: 'Buscar repositórios do GitHub...',
ajax: {
url: 'https://api.github.com/search/repositories',
method: 'GET',
delay: 300, // Delay de debounce
cache: true, // Habilitar cache
// Transformar parâmetros da requisição
data: (params) => ({
q: params.term,
page: params.page,
per_page: 10
}),
// Processar resposta
processResults: (data, params) => ({
results: data.items.map(repo => ({
id: repo.id,
text: repo.full_name,
description: repo.description,
stars: repo.stargazers_count
})),
pagination: {
more: data.items.length >= 10
}
}),
// Headers customizados
headers: {
'Authorization': 'Bearer SEU_TOKEN'
},
// Transport customizado (opcional)
transport: (params, config) => {
return fetch(config.url + '?' + new URLSearchParams(params))
.then(r => r.json());
}
}
});Eventos AJAX:
element.addEventListener('vs:ajaxLoading', (e) => {
console.log('Carregando...', e.detail.params);
});
element.addEventListener('vs:ajaxSuccess', (e) => {
console.log('Carregado:', e.detail.results);
});
element.addEventListener('vs:ajaxError', (e) => {
console.error('Erro:', e.detail.error);
});Tagging
Permita que usuários criem novas opções dinamicamente:
const select = new VanillaSmartSelect('#tags-select', {
tags: true,
multiple: true,
placeholder: 'Digite para adicionar tags...',
// Validar e criar tag
createTag: (params) => {
const term = params.term.trim();
if (!term) return null;
// Exemplo de validação de email
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(term)) {
return null;
}
return {
id: term,
text: term
};
},
// Controlar posição de inserção da tag
insertTag: (data, tag) => {
data.unshift(tag); // Adicionar no início
},
// Template customizado para opção de criação de tag
templateResult: (item) => {
if (item._isTag) {
return `<div style="color: #28a745;">✨ Criar tag: "${item.text}"</div>`;
}
return item.text;
}
});Casos de Uso:
- Destinatários de email
- Palavras-chave/Categorias
- Menções de usuário (@usuario)
- Filtros customizados
Paginação / Scroll Infinito
Carregue automaticamente mais resultados conforme o usuário rola:
const select = new VanillaSmartSelect('#pagination-select', {
ajax: {
url: 'https://api.github.com/search/users',
data: (params) => ({
q: params.term || 'john',
page: params.page || 1,
per_page: 15
}),
processResults: (data, params) => ({
results: data.items.map(user => ({
id: user.id,
text: user.login,
avatar: user.avatar_url
})),
pagination: {
more: data.items.length >= 15 // Tem mais páginas
}
})
}
});Como funciona:
- Role até o final → carrega automaticamente a próxima página
- Resultados são acumulados (página 1 + página 2 + página 3...)
- Indicador "Carregando mais..." mostrado durante o carregamento
- Mudança no termo de busca reseta a paginação
Referência da API
Métodos
Gerenciamento de Valor
// Obter valor atual
const value = select.val();
// Definir valor (seleção única)
select.val('option-id');
// Definir valor (multi-seleção)
select.val(['id1', 'id2', 'id3']);
// Limpar seleção
select.clear();Seleção Programática
// Selecionar item por ID
select.select('item-id');
// Desselecionar item (apenas multi-seleção)
select.unselect('item-id');
// Obter item(s) selecionado(s) com dados completos
const selected = select.getSelected();
// Retorna: { id, text, ...customData } ou [{ id, text }, ...]Gerenciamento de Dados
// Obter todos os dados
const data = select.data();
// Definir/Substituir dados
select.data([
{ id: 1, text: 'Nova Opção 1' },
{ id: 2, text: 'Nova Opção 2' }
]);
// Adicionar opção única
select.addOption({ id: 'new', text: 'Nova Opção' });
// Remover opção por ID
select.removeOption('option-id');Controle do Dropdown
// Abrir dropdown
select.open();
// Fechar dropdown
select.close();
// Alternar dropdown
select.toggle();
// Verificar se está aberto
const isOpen = select.isOpen(); // boolean
// Focar no select
select.focus();Habilitar/Desabilitar
// Desabilitar select
select.disable();
// Habilitar select
select.enable();Validação HTML5
// Verificar se é válido
const isValid = select.checkValidity(); // boolean
// Mostrar mensagem de validação
const isValid = select.reportValidity(); // boolean
// Definir erro customizado
select.setCustomValidity('Por favor, selecione uma opção');
select.setCustomValidity(''); // Limpar erro
// Obter mensagem de validação
const message = select.validationMessage();
// Verificar se será validado
const willValidate = select.willValidate();Internacionalização
import { getLanguage } from 'vanilla-smart-select/i18n';
// Alterar idioma dinamicamente
select.updateLanguage(getLanguage('pt-BR'));Limpeza
// Destruir instância e limpar
select.destroy();Eventos
Todos os eventos são prefixados com vs: para evitar conflitos.
Eventos Básicos
const element = document.querySelector('#mySelect');
// Eventos de seleção
element.addEventListener('vs:select', (e) => {
console.log('Selecionado:', e.detail.data);
});
element.addEventListener('vs:unselect', (e) => {
console.log('Desselecionado:', e.detail.data);
});
element.addEventListener('vs:change', (e) => {
console.log('Valor alterado:', e.detail.value);
});
element.addEventListener('vs:clear', (e) => {
console.log('Seleção limpa');
});
// Eventos do dropdown
element.addEventListener('vs:open', () => {
console.log('Dropdown aberto');
});
element.addEventListener('vs:close', () => {
console.log('Dropdown fechado');
});
// Eventos de busca
element.addEventListener('vs:query', (e) => {
console.log('Termo de busca:', e.detail.term);
});
element.addEventListener('vs:results', (e) => {
console.log('Resultados:', e.detail.results);
});
// Eventos de ciclo de vida
element.addEventListener('vs:init', () => {
console.log('Inicializado');
});
element.addEventListener('vs:destroy', () => {
console.log('Destruído');
});Eventos Avançados
// Limite de seleção (multi-seleção)
element.addEventListener('vs:selectionLimitReached', (e) => {
console.log('Limite atingido:', e.detail.maximum);
console.log('Mensagem:', e.detail.message);
});
// Eventos AJAX
element.addEventListener('vs:ajaxLoading', (e) => {
console.log('Carregando dados...', e.detail.params);
});
element.addEventListener('vs:ajaxSuccess', (e) => {
console.log('Dados carregados:', e.detail.results);
});
element.addEventListener('vs:ajaxError', (e) => {
console.error('Erro ao carregar dados:', e.detail.error);
});
// Eventos de dados
element.addEventListener('vs:dataLoaded', (e) => {
console.log('Dados carregados:', e.detail.data);
});Eventos Preveníveis
// Estes eventos podem ser prevenidos com e.preventDefault()
element.addEventListener('vs:selecting', (e) => {
if (shouldPrevent) {
e.preventDefault(); // Cancelar seleção
}
});
element.addEventListener('vs:unselecting', (e) => {
e.preventDefault(); // Cancelar desseleção
});
element.addEventListener('vs:clearing', (e) => {
e.preventDefault(); // Cancelar limpeza
});
element.addEventListener('vs:opening', (e) => {
e.preventDefault(); // Prevenir abertura do dropdown
});
element.addEventListener('vs:closing', (e) => {
e.preventDefault(); // Prevenir fechamento do dropdown
});Opções de Configuração
Lista completa de todas as opções disponíveis:
{
// ===== Opções de Exibição =====
placeholder: '', // Texto placeholder
theme: 'default', // Nome do tema
width: '100%', // Largura do select
containerCssClass: '', // Classe CSS customizada para container
dropdownCssClass: '', // Classe CSS customizada para dropdown
// ===== Opções de Comportamento =====
multiple: false, // Habilitar multi-seleção
searchable: true, // Habilitar busca
allowClear: false, // Mostrar botão limpar (seleção única)
disabled: false, // Desabilitar select
closeOnSelect: true, // Fechar dropdown após seleção
// ===== Opções de Dados =====
data: null, // Array de dados (alternativa a elementos <option>)
// ===== Opções de Busca =====
searchMinimumLength: 0, // Caracteres mínimos para buscar
searchDelay: 250, // Delay de debounce (ms)
searchPlaceholder: null, // Placeholder do input de busca
matcher: null, // Função matcher customizada
matchStrategy: 'contains', // 'startsWith' | 'contains' | 'exact'
// ===== Opções de Template =====
templateResult: null, // Função: (item) => HTMLElement | string
templateSelection: null, // Função: (item) => HTMLElement | string
escapeMarkup: (markup) => markup, // Função para sanitizar HTML
// ===== Opções do Dropdown =====
dropdownParent: null, // Elemento pai para o dropdown
dropdownAutoWidth: false, // Largura automática do dropdown
// ===== Opções AJAX =====
ajax: null, // Objeto de configuração AJAX
/*
ajax: {
url: '', // URL da API
method: 'GET', // Método HTTP
dataType: 'json', // Tipo de resposta: 'json' | 'text' | 'blob'
delay: 250, // Delay de debounce (ms)
cache: false, // Habilitar cache
headers: {}, // Headers customizados
data: (params) => params, // Transformar parâmetros da requisição
processResults: (data) => ({ results: data }), // Processar resposta
transport: null // Função fetch customizada
}
*/
// ===== Opções de Tagging =====
tags: false, // Habilitar tagging
createTag: (params) => { // Função criar tag
const term = params.term?.trim();
if (!term) return null;
return { id: term, text: term };
},
insertTag: (data, tag) => { // Função inserir tag
data.unshift(tag);
},
// ===== Opções de Idioma/i18n =====
language: { // Objeto de idioma ou detecção automática
noResults: 'Nenhum resultado encontrado',
searching: 'Buscando...',
searchPlaceholder: 'Buscar...',
loading: 'Carregando...',
loadingMore: 'Carregando mais resultados...',
errorLoading: 'Os resultados não puderam ser carregados',
inputTooShort: (args) => `Por favor, digite ${args.minimum} ou mais caracteres`,
inputTooLong: (args) => `Por favor, delete ${args.excess} caracteres`,
maximumSelected: (args) => `Você só pode selecionar ${args.maximum} itens`,
createNewTag: (args) => `Criar tag: "${args.term}"`,
loadMore: 'Carregar mais resultados'
},
// ===== Opções de Acessibilidade =====
ariaLabel: null, // Label ARIA
ariaDescribedBy: null, // ARIA described by
// ===== Opções de Seleção =====
maximumSelectionLength: 0, // Seleções máximas (0 = ilimitado, apenas multi-seleção)
// ===== Callbacks de Eventos =====
onOpen: null, // Função chamada ao abrir
onClose: null, // Função chamada ao fechar
onChange: null, // Função chamada ao alterar valor
onSelect: null, // Função chamada ao selecionar item
onUnselect: null, // Função chamada ao desselecionar item
onClear: null, // Função chamada ao limpar
// ===== Debug =====
debug: false // Habilitar modo debug
}Suporte a Navegadores
- ✅ Chrome/Edge (últimas 2 versões)
- ✅ Firefox (últimas 2 versões)
- ✅ Safari (últimas 2 versões)
- ✅ Opera (últimas 2 versões)
- ❌ Internet Explorer (não suportado)
Requisitos Mínimos:
- Suporte ES6+
- Fetch API
- CSS Grid
- CSS Custom Properties (variáveis)
Performance
- Tamanho do Bundle: ~50KB minificado, ~15KB gzipped
- Zero Dependências: Nenhuma biblioteca externa necessária
- Renderização Otimizada: Conceitos de Virtual DOM para performance
- Cache Inteligente: Respostas AJAX cacheadas automaticamente
- Busca com Debounce: Chamadas de API reduzidas
🚀 Melhorias Futuras
Ideias sendo consideradas para versões futuras:
- Virtual scrolling para 10k+ itens
- Drag & drop para reordenar seleções
- Sistema de temas avançado
- Definições TypeScript
- API de Plugin/Extensão
Sugestões? Abra uma discussão!
🤝 Contribuindo
Contribuições são bem-vindas! Seja correções de bugs, novos recursos, melhorias na documentação ou exemplos.
Como contribuir:
- Faça um fork do repositório
- Crie um branch de feature (
git checkout -b feature/recurso-incrivel) - Faça suas alterações
- Teste minuciosamente (execute exemplos, verifique compatibilidade)
- Commit suas alterações (
git commit -m 'Adiciona recurso incrível') - Push para o branch (
git push origin feature/recurso-incrivel) - Abra um Pull Request
Áreas onde ajuda é apreciada:
- 🐛 Correção de bugs
- 📝 Melhorias na documentação
- 🌍 Traduções de idiomas adicionais
- 💡 Novos exemplos
- 🎨 Temas
- ✅ Testes
Configuração de Desenvolvimento
# Clonar o repositório
git clone https://github.com/AiltonOcchi/vanilla-smart-select.git
cd vanilla-smart-select
# Instalar dependências
npm install
# Iniciar modo de desenvolvimento (rebuild automático em alterações)
npm run dev
# Build para produção
npm run build
# Executar testes
npm testLicença
Licença MIT - veja o arquivo LICENSE para detalhes
💬 Suporte e Comunidade
- 📚 Documentação: Você está lendo!
- 🐛 Relatórios de Bug: GitHub Issues
- 💡 Solicitações de Recursos: GitHub Discussions
- ⭐ Mostre Apoio: Dê uma estrela ao projeto no GitHub!
