Home

Introducing Sveltik


Sveltik is a Svelte library for forms. It was inspired by the React library Formik. The Svelte implementation evolved out of various snippets I’ve been using to manage form state in Svelte in client projects over the past couple years. The release of Svelte 3, specifically the let: directive, allowed for a rewrite with a much cleaner API.

Svelte has excellent built-in reactive support for forms and inputs, but once lifecycle methods like form submission and validation are included, things get a little verbose. Sveltik allows forms, inputs, validation and submission to be modularized behind a consistent API, while maintaining the high performance of Svelte’s built-in reactivity.

Sveltik compiles to ~11.2 kB gzipped.

Gist

Sveltik manages your form’s state and exposes it via let: directives to your HTML.

<script>
    import { Sveltik } from 'sveltik'

    const initialValues = { email: '', password: '' }

    const validate = values => {
        const errors = {}
        if (!values.email) {
            errors.email = 'Required'
        } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i.test(values.email)) {
            errors.email = 'Invalid email address'
        }
        return errors
    }

    const onSubmit = (values, { setSubmitting }) => {
        setTimeout(() => {
            alert(JSON.stringify(values, null, 2))
            setSubmitting(false)
        }, 400)
    }
</script>

<div>
    <Sveltik
        {initialValues}
        {validate}
        {onSubmit}
        let:values
        let:errors
        let:touched
        let:handleInput
        let:handleBlur
        let:handleSubmit
        let:isSubmitting
    >
        <form on:submit="{handleSubmit}">
            <input type="email" name="email" value="{values.email}" on:input="{handleInput}" on:blur="{handleBlur}" />
            {#if errors.email && touched.email} {errors.email} {/if}
            <input
                type="password"
                name="password"
                value="{values.password}"
                on:input="{handleInput}"
                on:blur="{handleBlur}"
            />
            {#if errors.password && touched.password} {errors.password} {/if}
            <button type="submit" disabled="{isSubmitting}">
                Submit
            </button>
        </form>
    </Sveltik>
</div>

Reducing boilerplate

The verbose example above is explicit about how DOM events update the form state. While this low-level API is powerful for complex forms, a lot of the boilerplate can be simplified using Form, Field and ErrorMessage components.

<script>
    import { Sveltik, Form, Field, ErrorMessage } from 'sveltik'

    const initialValues = { email: '', password: '' }

    const validate = values => {
        const errors = {}
        if (!values.email) {
            errors.email = 'Required'
        } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i.test(values.email)) {
            errors.email = 'Invalid email address'
        }
        return errors
    }

    const onSubmit = (values, { setSubmitting }) => {
        setTimeout(() => {
            alert(JSON.stringify(values, null, 2))
            setSubmitting(false)
        }, 400)
    }
</script>

<div>
    <Sveltik {initialValues} {validate} {onSubmit} let:isSubmitting>
        <form>
            <Field type="email" name="email" />
            <ErrorMessage name="email" as="div" />
            <Field type="password" name="password" />
            <ErrorMessage name="password" as="div" />
            <button type="submit" disabled="{isSubmitting}">
                Submit
            </button>
        </form>
    </Sveltik>
</div>

Low-level API

Just like the <Sveltik> component exposes state via let: directives, <Form> and <Field> do was well. Custom inputs or interfaces with other components can be created with the low-level API:

<Field name="email" let:field>
    <input
        name="{field.name}"
        value="{field.value}"
        type="email"
        on:input="{field.handleInput}"
        on:blur="{field.handleBlur}"
    />
</Field>

A lot more functionality is possible, read the full docs in the README on GitHub.

Looking forward

I have been very satisfied with the performance of Sveltik in production. It’s currently deployed on 6 client projects, with 2 more coming online soon.

There are a few Svelte bugs that currently keep the bundle size at around 11 kB. Once these are solved, I’m estimating that the bundle size should drop by around 30%.

This issue requires a workaround in Sveltik, rendering an entirely separate component when <Field> is used to create a <select multiple> element or a <input type="number" /> element.

This issue requires Sveltik to output HTML instead of DOM elements for <ErrorMessage /> There are multiple workarounds but the dynamic elements proposal is the cleanest solution.

Roadmap

Although I haven’t needed it yet internally, the next step on the roadmap is adding support for Yup for form validations.


Stay in touch

The next post will be about delivering maps via a serverless proxy with Cloudflare Workers. Subscribe to the email list to get notified when it's published. No spam, ever.

Your email address will never be shared or sold.