Implementation
Vue 3
vue-countries-flags
Vue 3 component for <script setup> with
v-model and @change.
Try it live
Open the Vue demo page to try the component.
Styling examples
Go to the Vue styling section for Tailwind, Bootstrap, Vuetify, inline styles, and the slot map.
Installation
pnpm add @drobinetm/vue-countries-flags flag-icons Import the CSS in your entry point:
// main.ts
import 'flag-icons/css/flag-icons.min.css'
import '@drobinetm/vue-countries-flags/styles' Basic usage
Use native v-model to keep the selected country code reactive.
<script setup lang="ts">
import { ref } from 'vue'
import { CountriesFlags } from '@drobinetm/vue-countries-flags'
const selectedCode = ref<string | null>(null)
</script>
<template>
<CountriesFlags
v-model="selectedCode"
placeholder="Select a country"
/>
<p>Selected: {{ selectedCode }}</p>
</template> Filter & limit
Use filter and max to narrow the list to the markets you need.
<CountriesFlags
v-model="selectedCode"
:filter="['es', 'us', 'br', 'ar', 'mx']"
:max="10"
/> @change event
The change event gives you the resolved country object in addition to the code.
import type { CountryChangeEvent } from '@drobinetm/vue-countries-flags'
function onSelect(event: CountryChangeEvent) {
console.log(event.code) // 'es'
console.log(event.country) // { code: 'es', name: 'Spain' }
}
<CountriesFlags v-model="code" @change="onSelect" /> CSS integrations
In Vue, you can theme the component with utility classes, design-system helpers, or raw inline styles. The examples below are valid for Vue apps only.
Tailwind works well here, and the same slot mapping also fits a shadcn-style design system.
<CountriesFlags
v-model="selectedCode"
:unstyled="true"
:class-names="{
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-emerald-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',
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-emerald-600 text-white',
optionName: 'truncate',
}"
:styles="{
flag: { borderRadius: '9999px' },
}"
/>
You can apply Bootstrap utilities through
classNames without wrapping the component.
<CountriesFlags
v-model="selectedCode"
:unstyled="true"
:class-names="{
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%' },
}"
/> The component is not a native Vuetify field, but you can align it with Vuetify spacing and surfaces through slot classes.
<CountriesFlags
v-model="selectedCode"
:unstyled="true"
:class-names="{
root: 'w-100',
trigger: 'd-flex align-center justify-space-between w-100 px-4 py-3 rounded-lg border',
triggerContent: 'd-flex align-center ga-2 min-w-0',
placeholder: 'text-medium-emphasis',
list: 'mt-2 pa-2 rounded-lg border bg-surface elevation-8',
option: 'd-flex align-center ga-2 rounded-lg px-3 py-2',
optionActive: 'bg-grey-lighten-4',
optionSelected: 'bg-primary text-white',
}"
:styles="{
flag: { borderRadius: '9999px' },
}"
/>
If your styles come from runtime values, bind them with the
styles prop.
<CountriesFlags
v-model="selectedCode"
:unstyled="true"
:styles="{
root: { width: '100%', minWidth: '280px' },
trigger: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
minHeight: '44px',
padding: '0 14px',
border: '1px solid #d4d4d8',
borderRadius: '16px',
backgroundColor: '#ffffff',
color: '#111827',
},
triggerContent: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 },
placeholder: { color: '#6b7280' },
list: {
marginTop: '8px',
padding: '6px',
listStyle: 'none',
border: '1px solid #e5e7eb',
borderRadius: '16px',
backgroundColor: '#ffffff',
boxShadow: '0 24px 48px rgba(15, 23, 42, 0.18)',
},
option: { display: 'flex', alignItems: 'center', gap: '8px', padding: '10px 12px', borderRadius: '12px' },
optionActive: { backgroundColor: '#f3f4f6' },
optionSelected: { backgroundColor: '#059669', color: '#ffffff' },
flag: { borderRadius: '9999px' },
}"
/> Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue (v-model) | string | null | null | Selected country code. |
max | number | 0 | Max countries shown. |
filter | string[] | [] | ISO alpha-2 whitelist. |
placeholder | string | "Select a country" | Placeholder text. |
disabled | boolean | false | Disables the component. |
unstyled | boolean | false | Turns off the bundled UI styles. |
classNames | CountriesFlagsClassNames | {} | Classes by component slot. |
styles | CountriesFlagsStyles | {} | Inline styles by component slot. |
Emits
| Event | Payload | Description |
|---|---|---|
update:modelValue | string | null | v-model binding update. |
change | CountryChangeEvent | Full { country, code } event. |