Frameworks

Guides for popular frameworks. All examples use the fill style — swap fill for flat, line, or monochrome to use a different style.

React

SVG as image

The simplest approach — render the SVG as an <img> tag:

interface WeatherIconProps {
    slug: string;
    style?: 'fill' | 'flat' | 'line' | 'monochrome';
    size?: number;
}

function WeatherIcon({ slug, style = 'fill', size = 64 }: WeatherIconProps) {
    return (
        <img
            src={`/icons/${style}/${slug}.svg`}
            alt={slug}
            width={size}
            height={size}
        />
    );
}

// Usage
<WeatherIcon slug="rain" />
<WeatherIcon slug="snow" style="line" size={48} />

SVG inline

For full CSS control, fetch and inject the SVG markup:

import { useEffect, useRef } from 'react';

function InlineSvgIcon({ slug, style = 'fill', size = 64 }: WeatherIconProps) {
    const containerRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
        const controller = new AbortController();

        fetch(`/icons/${style}/${slug}.svg`, { signal: controller.signal })
            .then(res => res.text())
            .then(svg => {
                if (containerRef.current) {
                    containerRef.current.innerHTML = svg;
                }
            });

        return () => controller.abort();
    }, [slug, style]);

    return <div ref={containerRef} style={{ width: size, height: size }} />;
}

Lottie

bun add @meteocons/lottie lottie-react
npm install @meteocons/lottie lottie-react
yarn add @meteocons/lottie lottie-react
pnpm add @meteocons/lottie lottie-react

Static import — the animation JSON is bundled at build time:

import Lottie from 'lottie-react';
import rainAnimation from '@meteocons/lottie/fill/rain.json';

function RainIcon() {
    return (
        <Lottie
            animationData={rainAnimation}
            loop
            autoplay
            style={{ width: 64, height: 64 }}
        />
    );
}

Dynamic import — load the animation at runtime, useful when the icon slug is a prop:

import { useEffect, useState } from 'react';
import Lottie from 'lottie-react';

function DynamicWeatherIcon({ slug, style = 'fill', size = 64 }: WeatherIconProps) {
    const [animationData, setAnimationData] = useState(null);

    useEffect(() => {
        const controller = new AbortController();

        fetch(`/icons/${style}/${slug}.json`, { signal: controller.signal })
            .then(res => res.json())
            .then(setAnimationData);

        return () => controller.abort();
    }, [slug, style]);

    if (!animationData) {
        return <div style={{ width: size, height: size }} />;
    }

    return (
        <Lottie
            animationData={animationData}
            loop
            autoplay
            style={{ width: size, height: size }}
        />
    );
}

Vue

SVG as image

<script setup lang="ts">
defineProps<{
    slug: string
    style?: 'fill' | 'flat' | 'line' | 'monochrome'
    size?: number
}>();
</script>

<template>
    <img
        :src="`/icons/${style ?? 'fill'}/${slug}.svg`"
        :alt="slug"
        :width="size ?? 64"
        :height="size ?? 64"
    />
</template>

SVG inline

<script setup lang="ts">
import { ref, watchEffect } from 'vue';

const props = defineProps<{
    slug: string
    style?: 'fill' | 'flat' | 'line' | 'monochrome'
    size?: number
}>();

const svgMarkup = ref('');

watchEffect(async (onCleanup) => {
    const controller = new AbortController();
    onCleanup(() => controller.abort());

    const response = await fetch(
        `/icons/${props.style ?? 'fill'}/${props.slug}.svg`,
        { signal: controller.signal }
    );
    svgMarkup.value = await response.text();
});
</script>

<template>
    <div
        v-html="svgMarkup"
        :style="{ width: `${size ?? 64}px`, height: `${size ?? 64}px` }"
    />
</template>

Lottie

bun add @meteocons/lottie lottie-web
npm install @meteocons/lottie lottie-web
yarn add @meteocons/lottie lottie-web
pnpm add @meteocons/lottie lottie-web
<script setup lang="ts">
import lottie from 'lottie-web';
import type { AnimationItem } from 'lottie-web';
import { onMounted, onUnmounted, ref } from 'vue';

const props = defineProps<{
    slug: string
    style?: 'fill' | 'flat' | 'line' | 'monochrome'
    size?: number
}>();

const container = ref<HTMLElement>();
let animation: AnimationItem;

onMounted(() => {
    if (!container.value) {
        return;
    }

    animation = lottie.loadAnimation({
        container: container.value,
        renderer: 'svg',
        loop: true,
        autoplay: true,
        path: `/icons/${props.style ?? 'fill'}/${props.slug}.json`
    });
});

onUnmounted(() => animation?.destroy());
</script>

<template>
    <div
        ref="container"
        :style="{ width: `${size ?? 64}px`, height: `${size ?? 64}px` }"
    />
</template>

Svelte

SVG as image

<script lang="ts">
    let { slug, style = 'fill', size = 64 }: {
        slug: string;
        style?: 'fill' | 'flat' | 'line' | 'monochrome';
        size?: number;
    } = $props();
</script>

<img
    src="/icons/{style}/{slug}.svg"
    alt={slug}
    width={size}
    height={size}
/>

Lottie

bun add @meteocons/lottie lottie-web
npm install @meteocons/lottie lottie-web
yarn add @meteocons/lottie lottie-web
pnpm add @meteocons/lottie lottie-web
<script lang="ts">
    import lottie from 'lottie-web';

    let { slug, style = 'fill', size = 64 }: {
        slug: string;
        style?: 'fill' | 'flat' | 'line' | 'monochrome';
        size?: number;
    } = $props();

    let container: HTMLElement;

    $effect(() => {
        const animation = lottie.loadAnimation({
            container,
            renderer: 'svg',
            loop: true,
            autoplay: true,
            path: `/icons/${style}/${slug}.json`
        });

        return () => animation.destroy();
    });
</script>

<div bind:this={container} style="width: {size}px; height: {size}px" />

Angular

SVG as image

import { Component, Input } from '@angular/core';

@Component({
    selector: 'weather-icon',
    standalone: true,
    template: `
        <img
            [src]="'/icons/' + style + '/' + slug + '.svg'"
            [alt]="slug"
            [width]="size"
            [height]="size"
        />
    `
})
export class WeatherIconComponent {
    @Input({ required: true }) slug!: string;
    @Input() style: 'fill' | 'flat' | 'line' | 'monochrome' = 'fill';
    @Input() size = 64;
}

Lottie

bun add @meteocons/lottie lottie-web
npm install @meteocons/lottie lottie-web
yarn add @meteocons/lottie lottie-web
pnpm add @meteocons/lottie lottie-web
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import lottie from 'lottie-web';
import type { AnimationItem } from 'lottie-web';

@Component({
    selector: 'weather-lottie-icon',
    standalone: true,
    template: `
        <div
            #container
            [style.width.px]="size"
            [style.height.px]="size"
        ></div>
    `
})
export class WeatherLottieIconComponent implements OnInit, OnDestroy {
    @ViewChild('container', { static: true }) container!: ElementRef<HTMLElement>;
    @Input({ required: true }) slug!: string;
    @Input() style: 'fill' | 'flat' | 'line' | 'monochrome' = 'fill';
    @Input() size = 64;

    private animation?: AnimationItem;

    ngOnInit() {
        this.animation = lottie.loadAnimation({
            container: this.container.nativeElement,
            renderer: 'svg',
            loop: true,
            autoplay: true,
            path: `/icons/${this.style}/${this.slug}.json`
        });
    }

    ngOnDestroy() {
        this.animation?.destroy();
    }
}

Solid

SVG as image

import type { Component } from 'solid-js';

const WeatherIcon: Component<{
    slug: string;
    style?: 'fill' | 'flat' | 'line' | 'monochrome';
    size?: number;
}> = (props) => {
    return (
        <img
            src={`/icons/${props.style ?? 'fill'}/${props.slug}.svg`}
            alt={props.slug}
            width={props.size ?? 64}
            height={props.size ?? 64}
        />
    );
};

Lottie

bun add @meteocons/lottie lottie-web
npm install @meteocons/lottie lottie-web
yarn add @meteocons/lottie lottie-web
pnpm add @meteocons/lottie lottie-web
import { onCleanup, onMount } from 'solid-js';
import lottie from 'lottie-web';
import type { AnimationItem } from 'lottie-web';

function WeatherLottieIcon(props: {
    slug: string;
    style?: 'fill' | 'flat' | 'line' | 'monochrome';
    size?: number;
}) {
    let container!: HTMLDivElement;
    let animation: AnimationItem;

    onMount(() => {
        animation = lottie.loadAnimation({
            container,
            renderer: 'svg',
            loop: true,
            autoplay: true,
            path: `/icons/${props.style ?? 'fill'}/${props.slug}.json`
        });
    });

    onCleanup(() => animation?.destroy());

    return (
        <div
            ref={container}
            style={{ width: `${props.size ?? 64}px`, height: `${props.size ?? 64}px` }}
        />
    );
}

Astro

SVG as image

---
interface Props {
    slug: string;
    style?: 'fill' | 'flat' | 'line' | 'monochrome';
    size?: number;
}

const { slug, style = 'fill', size = 64 } = Astro.props;
---
<img src={`/icons/${style}/${slug}.svg`} alt={slug} width={size} height={size} />

Lottie with client islands

Astro is static by default, so Lottie animations need a client-side framework island. Use the Vue or React Lottie component from above with client:visible to load the animation only when it scrolls into view:

---
import WeatherLottieIcon from '../components/WeatherLottieIcon.vue';
---
<WeatherLottieIcon slug="rain" client:visible />

Next.js

SVG with Image component

import Image from 'next/image';
import clearDay from '@meteocons/svg/fill/clear-day.svg';

export default function Weather() {
    return (
        <Image
            src={clearDay}
            alt="Clear day"
            width={64}
            height={64}
        />
    );
}

Lottie with dynamic import

bun add @meteocons/lottie lottie-react
npm install @meteocons/lottie lottie-react
yarn add @meteocons/lottie lottie-react
pnpm add @meteocons/lottie lottie-react

Since Lottie requires browser APIs, use a dynamic import with ssr: false:

'use client';

import dynamic from 'next/dynamic';
import rainAnimation from '@meteocons/lottie/fill/rain.json';

const Lottie = dynamic(() => import('lottie-react'), { ssr: false });

export default function RainIcon() {
    return (
        <Lottie
            animationData={rainAnimation}
            loop
            autoplay
            style={{ width: 64, height: 64 }}
        />
    );
}

Nuxt

SVG with Vite import

Vite handles SVG imports out of the box:

<script setup lang="ts">
import clearDay from '@meteocons/svg/fill/clear-day.svg';
</script>

<template>
    <img :src="clearDay" alt="Clear day" width="64" height="64" />
</template>

Lottie with client-only

bun add @meteocons/lottie lottie-web
npm install @meteocons/lottie lottie-web
yarn add @meteocons/lottie lottie-web
pnpm add @meteocons/lottie lottie-web

Wrap the Lottie component in <ClientOnly> to avoid SSR issues:

<script setup lang="ts">
import lottie from 'lottie-web';
import type { AnimationItem } from 'lottie-web';
import { onMounted, onUnmounted, ref } from 'vue';

const props = defineProps<{ slug: string; style?: string }>();
const container = ref<HTMLElement>();
let animation: AnimationItem;

onMounted(() => {
    if (!container.value) {
        return;
    }

    animation = lottie.loadAnimation({
        container: container.value,
        renderer: 'svg',
        loop: true,
        autoplay: true,
        path: `/icons/${props.style ?? 'fill'}/${props.slug}.json`
    });
});

onUnmounted(() => animation?.destroy());
</script>

<template>
    <ClientOnly>
        <div ref="container" style="width: 64px; height: 64px" />
    </ClientOnly>
</template>