Open source @drobinetm/countries-flags

Library

React 18+
react-countries-flags

Controlled React component with forwardRef, typed props, and country data from the core package.

Try it live

Open the React demo page to try the component.

Open React demo

Styling examples

Go to the React styling section for Tailwind, shadcn/ui, Bootstrap, inline styles, and the slot map.

Open React styling

Installation

pnpm add @drobinetm/react-countries-flags flag-icons
Copied

Import the CSS in your entry point:

// main.tsx or App.tsx
import 'flag-icons/css/flag-icons.min.css'
import '@drobinetm/react-countries-flags/styles'
Copied

Basic usage

Use the controlled API with value and onChange.

import { useState } from 'react'
import { CountriesFlags } from '@drobinetm/react-countries-flags'

export function App() {
  const [code, setCode] = useState<string | null>(null)

  return (
    <CountriesFlags
      value={code}
      onChange={(event) => setCode(event.code)}
      placeholder="Select a country"
    />
  )
}
Copied

Filter & limit

Combine filter and max to expose only the countries your flow needs.

<CountriesFlags
  value={code}
  onChange={(event) => setCode(event.code)}
  filter={['es', 'us', 'br', 'ar', 'mx']}
  max={10}
/>
Copied

Access the full Country object

The callback receives both the ISO code and the resolved country object.

import type { CountryChangeEvent } from '@drobinetm/react-countries-flags'

function handleChange(event: CountryChangeEvent) {
  console.log(event.code)    // 'es'
  console.log(event.country) // { code: 'es', name: 'Spain' }
}

<CountriesFlags value={code} onChange={handleChange} />
Copied

CSS integrations

In React, you can keep the bundled styles, override specific slots, or disable them completely with unstyled. The examples below are valid for React projects only.

Use classNames and styles to map the component to Tailwind or shadcn/ui.

import { CountriesFlags } from '@drobinetm/react-countries-flags'

<CountriesFlags
  value={code}
  onChange={(event) => setCode(event.code)}
  unstyled
  classNames={{
    root: 'w-full',
    trigger:
      'flex h-11 w-full items-center justify-between rounded-xl border border-slate-300 bg-white px-3 text-left text-slate-900 shadow-sm transition focus:outline-none focus:ring-2 focus:ring-sky-500 dark:border-slate-700 dark:bg-slate-950 dark:text-slate-100',
    triggerContent: 'flex min-w-0 items-center gap-2',
    placeholder: 'text-slate-400 dark:text-slate-500',
    label: 'truncate',
    chevron: 'text-slate-500',
    list:
      'mt-2 max-h-64 overflow-auto rounded-xl border border-slate-200 bg-white p-1 shadow-2xl dark:border-slate-800 dark:bg-slate-950',
    option: 'flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm',
    optionActive: 'bg-slate-100 dark:bg-slate-900',
    optionSelected: 'bg-sky-600 text-white',
    optionName: 'truncate',
  }}
  styles={{
    flag: { borderRadius: '9999px' },
  }}
/>
Copied

You can also map the slots to Bootstrap utility classes.

import { CountriesFlags } from '@drobinetm/react-countries-flags'

<CountriesFlags
  value={code}
  onChange={(event) => setCode(event.code)}
  unstyled
  classNames={{
    root: 'w-100',
    trigger: 'btn btn-outline-secondary w-100 d-flex align-items-center justify-content-between rounded-3 px-3 py-2',
    triggerContent: 'd-flex align-items-center gap-2 text-truncate',
    placeholder: 'text-secondary',
    list: 'mt-2 list-unstyled border rounded-3 bg-body shadow-lg p-1',
    option: 'd-flex align-items-center gap-2 rounded-3 px-3 py-2',
    optionActive: 'bg-light',
    optionSelected: 'bg-primary text-white',
    optionName: 'text-truncate',
  }}
  styles={{
    root: { minWidth: '280px' },
    flag: { borderRadius: '50%' },
  }}
/>
Copied

If your styles are generated at runtime, use the styles prop directly.

import { CountriesFlags } from '@drobinetm/react-countries-flags'

<CountriesFlags
  value={code}
  onChange={(event) => setCode(event.code)}
  unstyled
  styles={{
    root: { width: '100%', minWidth: 280 },
    trigger: {
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'space-between',
      width: '100%',
      minHeight: 44,
      padding: '0 14px',
      border: '1px solid #d4d4d8',
      borderRadius: 16,
      backgroundColor: '#ffffff',
      color: '#111827',
    },
    triggerContent: { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 },
    placeholder: { color: '#6b7280' },
    list: {
      marginTop: 8,
      padding: 6,
      listStyle: 'none',
      border: '1px solid #e5e7eb',
      borderRadius: 16,
      backgroundColor: '#ffffff',
      boxShadow: '0 24px 48px rgba(15, 23, 42, 0.18)',
    },
    option: { display: 'flex', alignItems: 'center', gap: 8, padding: '10px 12px', borderRadius: 12 },
    optionActive: { backgroundColor: '#f3f4f6' },
    optionSelected: { backgroundColor: '#2563eb', color: '#ffffff' },
    flag: { borderRadius: '9999px' },
  }}
/>
Copied

Props

PropTypeDefaultDescription
valuestring | nullnullControlled selected code.
onChange(e: CountryChangeEvent) => voidSelection callback.
maxnumber0Max countries shown.
filterstring[][]ISO alpha-2 whitelist.
placeholderstring"Select a country"Placeholder text.
disabledbooleanfalseDisables the component.
unstyledbooleanfalseTurns off the bundled UI styles.
classNamestring""Additional CSS class names.
classNamesCountriesFlagsClassNames{}Classes by component slot.
stylesCountriesFlagsStyles{}Inline styles by component slot.