Building a Currency Input Component with shadcn-svelte and Svelte 5

Learn how to create a reusable currency input component using shadcn-svelte and Svelte 5's new runes syntax

Updated
Avatar Chris Jayden
Chris Jayden

On This Page

    Love Svelte content? ❤️

    Get the tastiest bits of Svelte content delivered to your inbox. No spam, no fluffy content — just the good stuff.

    Creating a user-friendly currency input component can be tricky. In this guide, we'll build a reusable currency input component using shadcn-svelte and Svelte 5's new runes syntax. The component will handle formatting, proper decimal places, and currency symbols automatically.

    Features

    • Automatic currency formatting
    • Configurable currency and locale
    • Proper decimal handling
    • Currency symbol display
    • Full keyboard support
    • Compatible with shadcn-svelte's theming

    Implementation

    Let's create our currency input component. We'll build it on top of shadcn-svelte's base Input component and add currency-specific functionality.

    src/lib/components/ui/currency-input/currency-input.svelte
    	<script lang="ts">import { Input } from "$components/ui/input/index.js";
    import { cn } from "$lib/utils/ui.js";
    let { ref = $bindable(null), value = $bindable(""), currencyFormat = new Intl.NumberFormat("de-DE", {
        style: "currency",
        currency: "EUR",
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    }), ...restProps } = $props();
    let currencySymbol = $derived.by(() => {
        const formatted = currencyFormat.format(0);
        return formatted.replace(/[d.,s]/g, "").trim();
    });
    function formatCurrency(value2) {
        return currencyFormat.format(value2).replace(currencySymbol, "").trim();
    }
    function handleInput(event) {
        const target = event.target;
        const numericValue = Number(target.value.replace(/D/g, "")) / 100;
        value = formatCurrency(numericValue);
    }
    function handleFocus(event) {
        const target = event.target;
        target.setSelectionRange(target.value.length, target.value.length);
    }
    </script>
     
    <div class="flex-auto">
        <div class="relative flex items-center">
            <div class="text-muted-foreground pointer-events-none absolute left-3 flex items-center">
                {currencySymbol}
            </div>
     
            <Input
                bind:value
                placeholder="0,00"
                autocomplete="off"
                maxlength={22}
                inputmode="decimal"
                type="text"
                class={cn('pl-6', restProps.class)}
                oninput={handleInput}
                onfocus={handleFocus}
                {ref}
                {...restProps}
            />
        </div>
    </div>
    

    How It Works

    Let's break down the key features of our currency input component:

    1. Props and Types

    The component accepts standard HTML input attributes plus some custom props:

    • variant: Matches shadcn's input variants
    • inputSize: Controls input size
    • currencyFormat: Allows customizing the currency format

    2. Currency Formatting

    We use the Intl.NumberFormat API for robust currency formatting:

    	currencyFormat = new Intl.NumberFormat('de-DE', {
        style: 'currency',
        currency: 'EUR',
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
    });
    

    3. Currency Symbol Extraction

    The currency symbol is automatically extracted using a derived state:

    	let currencySymbol = $derived.by(() => {
        const formatted = currencyFormat.format(0);
        return formatted.replace(/[d.,s]/g, '').trim();
    });
    

    4. Input Handling

    The component handles input events to maintain proper formatting:

    	function handleInput(event: Event) {
        const target = event.target as HTMLInputElement;
        const numericValue = Number(target.value.replace(/D/g, '')) / 100;
        value = formatCurrency(numericValue);
    }
    

    Usage

    Here's how to use the component in your Svelte application:

    	<script lang="ts">import { CurrencyInput } from "$components/ui/currency-input";
    let price = "";
    </script>
     
    <CurrencyInput bind:value={price} />
    

    Custom Currency Format

    You can customize the currency and locale:

    	<CurrencyInput
        currencyFormat={new Intl.NumberFormat('en-US', {
            style: 'currency',
            currency: 'USD',
            minimumFractionDigits: 2,
            maximumFractionDigits: 2
        })}
    />
    

    Styling

    The component inherits shadcn-svelte's theming system, so it will automatically match your application's theme. You can also customize the appearance using the class prop:

    	<CurrencyInput class="w-full max-w-xs" />
    

    Wrapping up

    This currency input component provides a robust solution for handling monetary values in your Svelte applications. It combines the power of shadcn-svelte's components with proper currency formatting and user-friendly features. The component is fully typed and takes advantage of Svelte 5's new runes syntax for better reactivity handling.

    Feel free to extend this component further based on your needs, such as adding validation, different currency symbols positioning, or additional formatting options.