Skip to content

A complete form solution for Svelte, with a Formik inspired API

Notifications You must be signed in to change notification settings

0livare/formsvelte

Repository files navigation

Formsvelte

A complete form solution for Svelte, with an API inspired by Formik.

This project is a work in progress. Features still to be developed:

  • Support for field arrays
  • A mechanism for accessing the <Formsvelte> context in the same component in which it is declared

Install

# Install with npm
npm i formsvelte

# Or install with yarn
yarn add formsvelte

Why?

Svelte has great built in bindings that make working with inputs much simpler than doing so would be in React (for example). But it's missing a solution for validation and error handling.

That's where Formsvelte comes in! By creating a validation schema, you can describe the rules of your form and the error messages that should be displayed when those rules aren't met. Formsvelte then takes those rules, makes sure they're followed, and displays your error message when they're not.

Example

<script>
  import {Formsvelte, Form, Field, Error} from 'formsvelte'
  import { string, object } from 'yup'

  const schema = object().shape({
    username: string()
      .email()
      .required('Please enter username'),
    password: string()
      .min(12, 'Password must be at least 12 characters')
      .required('Please enter a password')
  })
</script>

<Formsvelte
  initialValues={{ username: '', password: '' }}
  yupSchema={schema}
  onSubmit={(values) => alert(`Submitted!\n${JSON.stringify(values, null, 2)}`)}
>
  <Form>
    <div>
      <Field type="text" name="username" />
      <Error name="username" />
    </div>

    <div>
      <Field type="password" name="password" />
      <Error name="password" />
    </div>

    <button>Submit</button>
  </Form>
</Formsvelte>

API

<Formsvelte>

The root of every Formsvelte form that supplies context to the rest of the components.

Prop name Type Default Required? Description
initialValues T - Yes The initial set of values for your form. The shape of this object should correspond to the names of the Fields in your form.
onSubmit (values: T) => void - Yes Callback invoked when the form is submitted
yupSchema AnySchema undefined No A Yup schema used to validate your form. Schema libraries other than Yup will be supported in the future.

<Form>

A replacement for the native <form> element, this component is necessary to capture the submit event. It takes no props other than an optional class.

<Field>

Every instance of Field renders an input of some kind. The type of that input is determined by the type prop.

Prop name Type Default Required? Description
name string - Yes The name of the input. This should map to the shape of a key within Formsvelte.initialValues via dot notation.
type 'text' | 'checkbox' | 'radio' | 'select' | 'email' | 'password' - Yes The type of input to render. type == 'select' accepts a default slot of <option>s.
value string undefined No The value of this input. Typically only necessary for radio groups and checkbox groups.

<Error>

Display an error message from your validation schema when the rules of the schema are not met.

Prop name Type Default Required? Description
name string - Yes The name of the input (or group) that this error corresponds to. This should map to the shape of a key within Formsvelte.initialValues via dot notation.

More Examples

Checkbox

The value for a checkbox is just a boolean.

<script>
  const schema = object().shape({terms: boolean().isTrue()})
</script>

<Formsvelte
  initialValues={{terms: false}}
  yupSchema={schema}
>
  <Form>
    <Field type="checkbox" name="terms" />
    <Error type="terms">
  </Form>
</Formsvelte>

Radio Button Group

Notice that each of the radio inputs has the same name prop but different value props. When submitted, values[name] will have the value of the selected radio button. In this example, values.scoops will have 1, 2, or 3 as its value (the validation schema prevents 4 from being chosen).

<script>
  const schema = object().shape({
    scoops: number().min(1).max(3).required(),
  })
</script>

<Formsvelte
  initialValues={{scoops: ''}}
  yupSchema={schema}
>
  <Form>
      <label>
        <Field type="radio" name="scoops" value="1" />
        One
      </label>
      <label>
        <Field type="radio" name="scoops" value="2" />
        Two
      </label>
      <label>
        <Field type="radio" name="scoops" value="3" />
        Three
      </label>
      <label>
        <Field type="radio" name="scoops" value="4" />
        Four
      </label>
      <Error name="scoops" />
  </Form>
</Formsvelte>

Select

For selects, the <option>s are passed to the default slot of <Field>.

<script>
  const schema = object().shape({
    car: string().oneOf(['mercedes', 'audi']).required(),
  })
</script>

<Formsvelte
  initialValues={{car: ''}}
  yupSchema={schema}
>
  <Form>
    <Field type="select" name="car">
      <option value="">-</option>
      <option value="volvo">Volvo</option>
      <option value="saab">Saab</option>
      <option value="mercedes">Mercedes</option>
      <option value="audi">Audi</option>
    </Field>
    <Error name="car" />
  </Form>
</Formsvelte>

Checkbox Group

Notice that each of the checkboxes in the group has the same name prop but different value props. When submitted, values[name] will be an array of the values of the selected checkboxes; this is different from a single checkbox whose value is a single boolean.

<script>
  const schema = object().shape({
    flavors: array()
      .of(string().oneOf(['vanilla', 'strawberry']))
      .min(1)
      .required(),
  })
</script>

<Formsvelte
  initialValues={{flavors: []}}
  yupSchema={schema}
>
  <Form>
    <label>
      <Field type="checkbox" name="flavors" value="vanilla" />
      Vanilla
    </label>
    <label>
      <Field type="checkbox" name="flavors" value="chocolate" />
      Chocolate
    </label>
    <label>
      <Field type="checkbox" name="flavors" value="strawberry" />
      Strawberry
    </label>
    <Error name="flavors" />
  </Form>
</Formsvelte>

Nested objects

Arbitrary levels of nesting are supported via dot notation in the name prop of Fields and Errors.

<script>
  const schema = object().shape({
    foo: object().shape({
      name: string().required('You forgot this one'),
      terms: boolean().isTrue('Please accept terms').required('Accept my terms or else'),
    }),
  })
</script>

<Formsvelte
  initialValues={{foo: {name: '', terms: false}}}
  yupSchema={schema}
>
  <Form>
    <div>
      <label>
        What's your name?
        <Field type="text" name="foo.name" />
      </label>
      <Error name="foo.name" />
    </div>

    <div>
      <label>
        <Field type="checkbox" name="foo.terms" />
        Please accept the terms & conditions
      </label>
      <Error name="foo.terms" />
    </div>
  </Form>
</Formsvelte>

FAQ

Svelte throws a linting error about labels being associated with a control

A11y: A form label must be associated with a control.
svelte(a11y-label-has-associated-control)

Svelte doesn't know that Field renders an input, so it's mad that you're using a <label> without an input.

The easiest way I've found to address this is to add for={undefined} to the label.

<label for={undefined}>
  What is your name?
  <Field type="text" name="name" class="textbox" />
</label>

About

A complete form solution for Svelte, with a Formik inspired API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published