DEV Community

Cover image for How to add country icons to a Vue 3 app
Rushan
Rushan

Posted on Originally published at geoicons.io

How to add country icons to a Vue 3 app

Vue has no shortage of icon sets. Carets and chevrons come in every pack. Geography is the thin part of the shelf: where a set covers countries at all, it usually means flag squares rather than map shapes. GeoIcons ships country, area, and subdivision icons as individual Vue 3 components, 799 of them at the time of writing. Adding one takes three steps: install the package, import the icon by its country name, and render it.

npm i @geoicons/vue
Enter fullscreen mode Exit fullscreen mode
<script setup lang="ts">
import { UnitedStates, France, Japan } from '@geoicons/vue/countries';
</script>

<template>
  <UnitedStates :size="40" aria-label="United States" />
  <France :size="40" aria-label="France" />
  <Japan :size="40" aria-label="Japan" />
</template>
Enter fullscreen mode Exit fullscreen mode

See it live on geoicons.io →

That is the whole path. Below: sizing, color, accessibility, and picking an icon when you only know the country at runtime.

Key takeaways

  • Install @geoicons/vue, then import each country by its full name in PascalCase: UnitedStates, France, Japan.
  • Icons are Vue 3 components. Bind :size and :stroke-width; everything else falls through to the <svg>.
  • Icons are decorative by default. Pass aria-label only when the icon is the sole thing naming the country.
  • For a country chosen at runtime, pair a small component map with <component :is> so the bundle stays tree-shakable.

Step 1: Install the Vue package

Add the package to your project:

npm i @geoicons/vue
Enter fullscreen mode Exit fullscreen mode

It needs Vue 3.0 or newer and pulls in nothing else at runtime. On Vue 3.5+ each icon takes its ids from the built-in useId(), which stays stable across server and client renders. On 3.0 to 3.4 it falls back to a per-instance id, so older projects work without a flag.

The package ships as ES modules and sets "sideEffects": false. Vite reads your static imports at build time and keeps only the icons you named, so a three-icon app pays for three icons. How that works in detail.

Step 2: Import by country name

Each country is a named export, PascalCased: UnitedStates, France, Japan. Know the country and you know what to type, so autocomplete finishes the rest:

import { UnitedStates, France, Japan, Germany, Brazil } from '@geoicons/vue/countries';
Enter fullscreen mode Exit fullscreen mode

That line pulls the United States, France, Japan, Germany and Brazil. Reach for the /countries subpath rather than the package root: it opens the countries entry point directly, instead of a barrel that touches every category on the way through.

Not sure which name to import? Browse the full catalog. Each icon still carries its ISO 3166 alpha-2 code as an alias, Us alongside UnitedStates, if you need the shorter form.

Step 3: Render and style

Each import is an ordinary Vue component. Two props are declared, size and strokeWidth, and both accept a number or a string:

<template>
  <Germany :size="48" />
  <Germany :size="48" stroke="#2563eb" />
  <Germany :size="48" :stroke-width="1.5" />
  <Germany :size="48" fill="currentColor" />
</template>
Enter fullscreen mode Exit fullscreen mode
Prop What it does
size Width and height. Number or any CSS length. Defaults to 24.
strokeWidth Outline thickness. Bind as :stroke-width in templates. Defaults to 1.
stroke Outline color. Defaults to currentColor.
fill Fill color. Set to currentColor for a solid shape.
aria-label Accessible name. Omit for a decorative icon.

The same four, live:

See it live on geoicons.io →

In a template you bind the kebab-case :stroke-width. In a render function or JSX you write strokeWidth. Vue accepts both spellings, and the icons declare the camelCase one.

Everything else falls through

Only size and strokeWidth are declared props. stroke, fill, class, style, @click, data-* and any other attribute lands directly on the underlying <svg> through attribute fallthrough:

<UnitedStates class="text-blue-600 hover:text-blue-800" @click="select('us')" />
Enter fullscreen mode Exit fullscreen mode

Fallthrough is also why there is no color prop. Stroke defaults to currentColor, so each icon takes the color of the text around it. A theme switch that changes text color changes the icons with it, and no icon needs its own binding. The full dark-mode pattern.

Make it accessible

Icons are decorative by default. Each one renders with aria-hidden="true" unless you give it a name, so you do not need to add aria-hidden yourself:

<li><UnitedStates /> United States</li>
Enter fullscreen mode Exit fullscreen mode

The text already says "United States", so a screen reader should skip the icon. It does.

When the icon is the only thing conveying the country, pass aria-label. The component then switches to role="img", renders a <title>, and wires up aria-labelledby for you:

<button @click="select('us')">
  <UnitedStates aria-label="United States" />
</button>
Enter fullscreen mode Exit fullscreen mode

Because each instance derives its own id, rendering the same country twice on one page never produces duplicate <title> ids.

How do I render a country icon at runtime?

A country picker knows the country as a string, not as a component. GeoIcons has no <Icon name="us" /> lookup on purpose: a string API would force the package to retain every icon in the catalog in case you asked for one. Build the map yourself and hand it to Vue's dynamic component:

<script setup lang="ts">
import type { Component } from 'vue';
import { UnitedStates, France, Japan } from '@geoicons/vue/countries';

type Code = 'us' | 'fr' | 'jp';

// Three entries, because you imported three icons.
const byCode: Record<Code, Component> = { us: UnitedStates, fr: France, jp: Japan };

defineProps<{ code: Code }>();
</script>

<template>
  <component :is="byCode[code]" :size="20" />
</template>
Enter fullscreen mode Exit fullscreen mode

Declare Code as its own alias rather than deriving it with keyof typeof byCode. The defineProps macro resolves its type argument at compile time, and a plain local alias is the form it handles everywhere.

Vite still reads three static imports at the top of the file and prunes everything else. You get the runtime lookup without the catalog behind it.

Beyond countries: areas and subdivisions

The same import pattern covers the area icons, 167 of them today: continents, regions, landforms, and groupings like the European Union. There is no ISO code to reach for here, so the export name is a PascalCase slug instead:

<script setup lang="ts">
import { EuropeanUnion } from '@geoicons/vue/areas';
</script>

<template>
  <EuropeanUnion aria-label="European Union" />
</template>
Enter fullscreen mode Exit fullscreen mode

GeoIcons also ships subdivisions: states, provinces, and other first-level regions, 377 of them, imported from their own subpath. ISO 3166-2 codes are numeric for eleven Caribbean countries, so GeoIcons builds the export name from the parent country's alpha-2 code plus the region name instead, both PascalCase: UsTexas, JmKingston.

<script setup lang="ts">
import { UsTexas } from '@geoicons/vue/subdivisions';
</script>

<template>
  <UsTexas aria-label="Texas" />
</template>
Enter fullscreen mode Exit fullscreen mode

FAQ

How do I add a country icon to a Vue app?

Install @geoicons/vue, import the country by its full name in PascalCase, and render it as a component. For example, import { UnitedStates } from '@geoicons/vue/countries' gives you a component that takes a size and inherits its color from CSS.

How do I show an icon for a country chosen at runtime in Vue?

Import the countries you need, build a small object mapping codes to components such as { us: UnitedStates, fr: France }, and render it with Vue's dynamic component: <component :is=\

Does GeoIcons cover states and provinces, not just countries?

Yes, 377 subdivision icons alongside 255 countries and 167 areas. Import them from @geoicons/vue/subdivisions using the parent country's alpha-2 code plus the region name in PascalCase, such as UsTexas or JmKingston, since ISO 3166-2 codes are numeric for several Caribbean countries.

Do GeoIcons work with Nuxt and server-side rendering?

Yes. The icons render to plain SVG markup and touch no browser-only APIs, so the server can render them directly. On Vue 3.5 and newer the title ids come from Vue's built-in useId(), which returns the same id on the server and in the browser, so hydration does not mismatch.

Where to go next

The full prop and import reference lives in the GeoIcons API reference, or browse the full catalog to find the ones you need. Working in another framework? The same catalog ships for React, Angular, and vanilla JavaScript.

Top comments (0)