How to Build a Bootstrap Select with Search Using Tailwind CSS

You can implement a Bootstrap-style select with search functionality in Tailwind CSS by composing utility classes for the dropdown styling and JavaScript for the filtering logic, or by extracting reusable styles using the @apply directive and the plugin API.

The Tailwind CSS compiler, located in the tailwindlabs/tailwindcss repository, generates optimized CSS by parsing your markup and expanding utility classes at build time. To create a bootstrap select with search component, you leverage this utility-first architecture—combining layout, spacing, and color utilities with the framework's design system to build accessible, searchable dropdowns without writing custom CSS from scratch.

Understanding Tailwind's Compiler Architecture

Before building the component, it helps to understand how Tailwind processes your styles. The compiler transforms utility classes into optimized CSS through a series of discrete steps orchestrated by the design system.

Entry Point and Compile Flow

The public API lives in packages/tailwindcss/src/index.ts. It exposes compile(css, opts) which orchestrates the build pipeline:

  1. Parse the input CSS into an AST using CSS.parse.
  2. Collect Tailwind directives (@tailwind utilities, @theme, custom @utility rules) via parseCss.
  3. Build the Design System using buildDesignSystem, which holds the theme, generated utilities, and variant registry.
  4. Substitute at-rules (@apply, @import, @variant) and functions (theme(), color-mix()).
  5. Optimize the AST by removing unused utilities and merging selectors.
  6. Serialize the AST back into a CSS string.

Source: index.ts – compile flow

Design System and Theme Resolution

The buildDesignSystem function in packages/tailwindcss/src/design-system.ts creates three core objects that power every utility class:

Component Responsibility
Theme Stores CSS custom properties and resolves theme() calls, supporting prefixes and reference mode.
Utilities Generates utility rules from core configuration (e.g., bg-red-500, rounded).
Variants Handles responsive, state, and custom variants (e.g., hover:, focus-visible:).

The theme resolution logic resides in packages/tailwindcss/src/theme.ts, which manages CSS custom properties and the theme() function resolution used throughout the compiler.

Source: design-system.ts

Building a Bootstrap Select with Search Component

With the compiler architecture in mind, you can construct the searchable dropdown using three different approaches: pure utilities, @apply composition, or a custom plugin.

Pure Utility Class Implementation

The most straightforward method uses only Tailwind's utility classes in your HTML. This approach ensures maximum tree-shaking since only used utilities are included in the final bundle.

<div class="relative inline-block w-64">
  <!-- Search input -->
  <input
    type="text"
    placeholder="Search…"
    class="block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-sm leading-5 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
    id="search-input"
  />

  <!-- Dropdown list -->
  <ul
    class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm hidden"
    role="listbox"
    id="options-list"
  >
    <li
      class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900 hover:bg-indigo-100"
      role="option"
    >Apple</li>
    <li
      class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900 hover:bg-indigo-100"
      role="option"
    >Banana</li>
    <li
      class="relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900 hover:bg-indigo-100"
      role="option"
    >Cherry</li>
  </ul>
</div>

All classes are resolved by the createUtilities(theme) function in packages/tailwindcss/src/utilities.ts, which generates the CSS rules from your theme configuration.

Using @apply for Reusable Styles

For projects requiring repeated use of this pattern, the @apply directive—processed in packages/tailwindcss/src/apply.ts—lets you extract utility combinations into component classes.

Create a components.css file:

@layer components {
  .searchable-select-item {
    @apply relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900 hover:bg-indigo-100;
  }
}

Your HTML becomes cleaner:

<li class="searchable-select-item" role="option">Apple</li>

During compilation, the @apply rule is expanded to the underlying utility AST nodes, maintaining the same optimization benefits as inline utilities.

Creating a Custom Plugin

For the most maintainable solution, use the Plugin API defined in packages/tailwindcss/src/compat/plugin-api.ts. This allows you to register a dedicated .searchable-select component that generates all necessary styles.

Create tailwind.searchable-select.js:

module.exports = function ({ addComponents, theme }) {
  const components = {
    '.searchable-select': {
      '@apply relative inline-block w-64': {},
      'input': {
        '@apply block w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-sm leading-5 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500': {},
      },
      'ul': {
        '@apply absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm hidden': {},
      },
      'li': {
        '@apply relative cursor-default select-none py-2 pl-3 pr-9 text-gray-900 hover:bg-indigo-100': {},
      },
    },
  }
  addComponents(components)
}

Register it in tailwind.config.js:

module.exports = {
  content: ['./**/*.html'],
  plugins: [require('./tailwind.searchable-select.js')],
}

Now implement the component with a single class:

<div class="searchable-select">
  <input type="text" placeholder="Search…" />
  <ul role="listbox">
    <li role="option">Apple</li>
    <li role="option">Banana</li>
    <li role="option">Cherry</li>
  </ul>
</div>

The plugin leverages the same design-system → utilities pipeline described in buildDesignSystem, ensuring the generated CSS remains tree-shaken and theme-aware.

Utility Generation and Variant Handling

How Utilities Are Generated

The createUtilities(theme) function in packages/tailwindcss/src/utilities.ts iterates over the resolved theme (spacing, colors, fonts) and produces a map of class names to AST nodes. Each utility is cached for fast rebuilds, which is why the searchable select examples above compile quickly even with many utility combinations.

Adding Interactive States

Interactive states like hover:bg-indigo-100 or focus:ring-indigo-500 are handled by the variant system in packages/tailwindcss/src/variants.ts. Standard variants are defined here, and custom variants can be added via the plugin API using addVariant. The compiler records variant order to ensure generated CSS respects the same precedence as your source markup, which is critical for maintaining the correct cascade in your searchable dropdown's hover and focus states.

Summary

Frequently Asked Questions

How does Tailwind CSS differ from Bootstrap when building searchable select components?

Unlike Bootstrap's pre-built JavaScript components, Tailwind CSS is a utility-first framework that generates CSS at build time based on classes you use in your markup. According to the source code in packages/tailwindcss/src/index.ts, the compile() function parses your HTML, expands utilities via createUtilities() in packages/tailwindcss/src/utilities.ts, and outputs optimized CSS. This means you compose your searchable select from atomic utilities rather than overriding pre-defined component styles.

Can I use the @apply directive to create a reusable searchable select pattern?

Yes. The @apply directive, implemented in packages/tailwindcss/src/apply.ts, allows you to inline utility classes into custom CSS rules. You can create a .searchable-select-item class that uses @apply to bundle relative, cursor-default, select-none, and hover states. During compilation, Tailwind expands these references into the actual utility AST nodes, maintaining the same optimization benefits as inline utilities while keeping your HTML markup clean.

How do I add custom interactive states to my searchable select dropdown?

Interactive states like hover:bg-indigo-100 or focus:ring-indigo-500 are handled by the variant system defined in packages/tailwindcss/src/variants.ts. The compiler records variant order to ensure generated CSS respects the same precedence as your source markup. You can also register custom variants via the Plugin API in packages/tailwindcss/src/compat/plugin-api.ts using the addVariant function, allowing you to create bespoke states for your searchable select component while maintaining proper cascade order.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →