# Group

The group classification only contains the input type group. It is a special input used to logically group one or more fields together. It is useful for:

# Data organization

You can use the group type to logically group data. The fields nested under a group will be bundled together as their own object.

<FormulateForm
  v-model="fields"
>
  <FormulateInput
    type="text"
    name="name"
    label="Your name"
  />
  <FormulateInput
    type="group"
    name="address"
  >
    <FormulateInput
      type="text"
      name="address"
      label="Street address"
    />
    <FormulateInput
      type="text"
      name="city"
      label="City"
    />
    <FormulateInput
      type="select"
      name="state"
      label="State"
      :options="{VA: 'Virginia', CA: 'California', NY: 'New York'}"
    />
  </FormulateInput>
</FormulateForm>
{}

Note

For consistency, the value of a group will always be an array even if the field is not repeatable. If you need a plain object, we recommend destructuring the array inside a form submit handler.

# Repeatable groups

By setting the repeatable prop to true anything inside our group becomes infinitely repeatable including non-formulate elements. An "add more" button is also added, along with a remove button for each item. A repeatable group can be further customized with special props and slots.

View source code
<template>
  <div class="form-wrapper">
    <FormulateForm
      v-model="formData"
    >
      <FormulateInput
        type="group"
        name="attendees"
        :repeatable="true"
        label="Who is going to attend?"
        add-label="+ Add Attendee"
        validation="required"
      >
        <div class="attendee">
          <FormulateInput
            name="name"
            validation="required"
            label="Attendee’s name"
          />
          <FormulateInput
            type="email"
            name="email"
            validation="required|email"
            label="Attendee’s email"
          />
          <strong class="price" v-text="`$100`" />
        </div>
      </FormulateInput>
      <FormulateInput
        type="radio"
        label="Select your payment method"
        name="payment"
        :options="{paypal: 'PayPal', credit: 'Credit Card'}"
      />
      <strong>Total: {{ total }}</strong>
      <FormulateInput
        type="submit"
        label="Purchase tickets"
      />
    </FormulateForm>
    <code class="code code--block">{{ formData }}</code>
  </div>
</template>

<script>
export default {
  data () {
    return {
      formData: {}
    }
  },
  computed: {
    total () {
      const count = Array.isArray(this.formData.attendees) ? this.formData.attendees.length : 1
      return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD'}).format(count * 100)
    }
  }
}
</script>

<style scoped>
.form-wrapper {
  padding: 2em;
  border: 1px solid #a8a8a8;
  border-radius: .5em;
  box-sizing: border-box;
}
@media (min-width: 650px) {
  .attendee {
    display: flex;
  }
}

@media (min-width: 720px) {
  .attendee {
    display: block;
  }
}

@media (min-width: 850px) {
  .attendee {
    display: flex;
  }
  .attendee .formulate-input {
    margin-right: 1.5em;
  }
}
.attendee .formulate-input {
  margin-right: 2em;
  margin-bottom: 0;
}

strong {
  display: block;
  margin: 1em 0;
}

strong.price {
  margin-top: 1.25em;
  margin-bottom: 0;
  height: 2.5em;
  display: inline-flex;
  align-items: center;
}

code {
  margin-top: 2em;
}
</style>
Remove
$100
Total: $100.00
{}

Warning

It's easy to use groups to create very large forms with deep nesting, however bear in mind that all the inputs have dependencies on one another — doing so can create performance issues. If you notice performance issues, try leveraging the debounce prop on the form or inputs.

# Validation

By default, fields inside a group validate just like any other fields. Making a field inside a group required, for example, would prevent that form from submitting until that field has been completed. However, you can also place validation rules directly on the group. So placing a "required" rule on the group component ensures at least 1 repeatable item is in your group.

<FormulateForm>
  <FormulateInput
    type="group"
    label="Add your social media pages"
    :repeatable="true"
    validation-name="social media links"
    validation="min:2,length"
    add-label="+ Social link"
  >
    <FormulateInput
      label="Social media link"
      validation="required|url"
    />
  </FormulateInput>
  <FormulateInput
    type="submit"
  />
</FormulateForm>
Remove

Note

There is no blur event for a group type, so by default validation errors will be shown when someone attempts to submit a form. If you prefer errors to be immediately show, consider using error-behavior="live".

Note

The confirm-rule currently does not work within groups. This will be fixed in an upcoming release. See tracking issue(opens new window)

# Applying custom validation rules

Applying custom validation rules to a group field allows for very granular and powerful rules to meet your specific scenario. These rules give you access to the data in all of its sub-fields allowing complex cross-field validation.

View source code
<template>
  <FormulateForm
    class="form-wrapper"
    @submit="handle"
  >
    <FormulateInput
      name="name"
      label="Your name"
      placeholder="Your name"
      validation="required"
    />
    <FormulateInput
      label="Your shipping address"
      type="group"
      validation="address"
      :validation-rules="{ address: addressRule }"
      :validation-messages="{ address: addressMessage }"
    >
      <FormulateInput
        name="street"
        placeholder="Street address"
      />
      <div class="triple">
        <FormulateInput
          name="city"
          placeholder="City"
        />
        <FormulateInput
          type="select"
          name="state"
          :options="{VA: 'VA', PA: 'PA', WA: 'WA'}"
          placeholder="State"
        />
        <FormulateInput
          name="zip"
          placeholder="Zip"
        />
      </div>
    </FormulateInput>
    <FormulateInput
      type="submit"
    />
  </FormulateForm>
</template>

<script>
export default {
  methods: {
    addressRule ({ value }) {
      if (Array.isArray(value)) {
        const [address] = value
        return address.street && address.city && address.state && address.zip
      }
      return false
    },
    addressMessage ({ value }) {
      if (Array.isArray(value)) {
        const [address] = value
        const missing = ['street', 'city', 'state', 'zip'].reduce((missing, field) => {
          if (!address[field]) {
            missing.push(field)
          }
          return missing
        }, [])
        return `Your shipping address still requires: ${missing.join(', ')}.`
      }
      return 'Please fill out your shipping address.'
    },
    handle () {
      alert('All validation complete, form submitted.')
    }
  }
}
</script>

<style scoped>
.form-wrapper {
  padding: 2em;
  border: 1px solid #a8a8a8;
  border-radius: .5em;
  box-sizing: border-box;
  max-width: 450px;
}


.form-wrapper::v-deep .formulate-input-element {
  max-width: none;
}

@media (min-width: 650px) {
  .triple {
    display: flex;
  }

  .triple .formulate-input {
    margin-bottom: .25em;
    margin-right: 1em;
    flex-grow: 1;
  }

  .triple .formulate-input[data-classification="select"] {
    flex: 0 0 5em;
  }

  .triple .formulate-input:last-child {
    margin-right: 0;
  }
}

@media (min-width: 720px) {
  .triple {
    display: block;
  }

  .triple .formulate-input {
    margin-bottom: 1.5em;
    margin-right: 0;
  }
}

@media (min-width: 850px) {
  .triple {
    display: flex;
  }
  .triple .formulate-input {
    margin-bottom: .25em;
    margin-right: 1em;
  }

  .triple .formulate-input:last-child {
    margin-right: 0;
  }
}

</style>

# Settings errors on groups 2.5

When setting explicit errors on group input we need a way to indicate which index the errant field is at. To make this as simple as possible, use the group-errors prop along with "dot notation" to reference inputs in their index. For example:

<FormulateInput
  type="group"
  :repeatable="true"
  name="invitees"
  :group-errors="{
    '0.email': 'This email is already in use',
    '1.name': 'Pretty sure Rensmold isn’t a real last name'
  }"
  :value="[
    { name: 'Todd Berkins', email: 'todd@example.com' },
    { name: 'Stella Rensmold', email: 'stella@example.com' },
  ]"
>
  <FormulateInput
    name="name"
    label="Invitee's name"
  />
  <FormulateInput
    name="email"
    label="What is this user's email?"
  />
</FormulateInput>
Remove
  • This email is already in use.
Remove
  • Yeah...pretty sure “Rensmold” isn’t a real last name.

Notice how the errors always begin with the index of the group. The proeprties of the group-errors prop must always begin with the index of the group they are being applied to.

You can also apply group errors using dot notation from a <FormulateForm>.

# Index of current group

To manipulate a distinct group field, it is helpful to get the index of the current group item. Luckily, slots can help. The default slot for instance offers the index as a context variable of groupProps:

Example:

 <FormulateInput
    type="group"
    name="attendees"
    :repeatable="true"
    add-label="+ Add Attendee"
    #default="{ index }"
>
  <p>This is Group # {{ index }} </p>
  <FormulateInput
    name="price"
    disabled
    :value="getPrice(index)"
    label="Price"
  />
</FormulateInput>

# Props

The group field has a few unique props:

Prop Description
add-label When repeatable, this is the label to display on the "+ Add" button (defaults to label || name).
limit When repeatable, this is the maximum number of group items.
minimum When repeatable, this is the minimum number of group items.
remove-label When repeatable, this is the label to display on the "Remove" button.
remove-position Show the remove button before or after the group inputs (defaults to before)
repeatable Boolean indicates if the field is repeatable.
group-errors Object of dot notation properties (like 0.name) with errors.

# Slots

The group input has several unique slots (and matching Slot Components):

Slot name Description
addMore The add more button when repeatable.
The context object in this slot includes an addMore function that should be called to add new items.
default Anything in the default slot will be considered part of the group, and become repeatable if applicable. The context object will additionally have an "index" property.
grouping The primary repeatable area, responsible for rendering the inner content.
remove The remove button when repeatable.
The context object in this slot includes the index and a removeItem function that should be called to remove that item.
repeatable Responsible for rendering each row of inputs.
The context object in this slot includes a removeItem function that should be called to remove that item.

# Events 2.5

The group input type has two unique events:

Event name Description
@repeatableAdded Emitted when a new repeatable item is added to the group.
@repeatableRemoved Emitted when a repeatable item is removed from the group.

# Custom class keys

In addition to all global class keys following are available:

Key Default Description
grouping .formulate-input-grouping A wrapper around all repeatable fields (only exists when repeatable)
groupRepeatable .formulate-input-group-repeatable A wrapper around each set of grouped fields (exists even for non-repeatable groups).
groupRepeatableRemove .formulate-input-group-repeatable-remove The remove button for a field group.
groupAddMore .formulate-input-group-add-more The wrapper around the add more button for repeatable groups.