Limit array field to exactly 6 items and hide Add Item button

7 replies
Last updated: Apr 26, 2021
Is there a way to limit an array field to exactly 6 items and hide the
+ Add Item
button?
AI Update

Yes! You can limit an array field to exactly 6 items and hide the "Add Item" button. Here's how to do it:

Validation for exactly 6 items

First, add validation to your array field in the schema:

{
  name: 'myArray',
  type: 'array',
  of: [{type: 'string'}], // or whatever type you need
  validation: Rule => Rule.required().min(6).max(6)
}

This prevents publishing if the array doesn't have exactly 6 items. However, the "Add Item" button will still appear.

Hiding the "Add Item" button

To hide the button when the limit is reached, you'll need to create a custom array input component. Here's a solution that works with Sanity Studio v3:

1. Create a custom component file (e.g., customArrayFunctions.js):

import React from "react";
import {isReferenceSchemaType} from '@sanity/types'
import {AddIcon} from '@sanity/icons'
import {Button, Grid, Menu, MenuButton, MenuItem} from '@sanity/ui'
import {useId} from '@reach/auto-id'

export default function ArrayFunctions(props) {
  const {type, readOnly, children, onCreateValue, onAppendItem, value} = props
  const menuButtonId = useId()

  const insertItem = React.useCallback(
    (itemType) => {
      const item = onCreateValue(itemType)
      onAppendItem(item)
    },
    [onCreateValue, onAppendItem]
  )

  const handleAddBtnClick = React.useCallback(() => {
    insertItem(type.of[0])
  }, [type, insertItem])

  if (readOnly) {
    return null
  }

  // Check for max length validation
  const maxLength = type.validation[0]?._rules.find(
    (rule) => rule.flag === "max"
  );

  // Hide button if max length reached
  if (maxLength && value && value.length >= maxLength.constraint) {
    return null;
  }

  return (
    <Grid gap={1} style={{gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))'}}>
      {type.of.length === 1 ? (
        <Button icon={AddIcon} mode="ghost" onClick={handleAddBtnClick} text="Add item" />
      ) : (
        <MenuButton
          button={<Button icon={AddIcon} mode="ghost" text="Add item…" />}
          id={menuButtonId || ''}
          menu={
            <Menu>
              {type.of.map((memberDef, i) => {
                const referenceIcon =
                  isReferenceSchemaType(memberDef) &&
                  (memberDef.to || []).length === 1 &&
                  memberDef.to[0].icon

                const icon = memberDef.icon || memberDef.type?.icon || referenceIcon
                return (
                  <MenuItem
                    key={i}
                    text={memberDef.title || memberDef.type?.name}
                    onClick={() => insertItem(memberDef)}
                    icon={icon}
                  />
                )
              })}
            </Menu>
          }
        />
      )}
      {children}
    </Grid>
  );
}

2. Register the component in your sanity.config.js (Studio v3):

import {defineConfig} from 'sanity'
import ArrayFunctions from './customArrayFunctions'

export default defineConfig({
  // ... other config
  form: {
    components: {
      input: (props) => {
        if (props.schemaType.jsonType === 'array') {
          return <ArrayFunctions {...props} />
        }
        return props.renderDefault(props)
      }
    }
  }
})

This solution automatically reads your max() validation rule and hides the "Add Item" button when the array reaches that length. It works with both single-type and multi-type arrays, and uses Sanity UI components to maintain a consistent look with the rest of the Studio.

Credit goes to the Sanity community discussion where this approach was developed and refined. The original gist was created by a community member and adapted by others to work with Sanity UI.

Show original thread
7 replies
You could restrict your array to six and only six items with a validation like this (there are other ways, too):

validation: Rule => Rule.required().min(6).max(6)
This would prevent publishing the document in the studio if the number of items is not six. Note that this will not prevent more or less items when the document is created or edited outside of the studio (the CLI, a client, the HTTP API, etc.), as validation currently only works in the studio.

Removing the
Add item
button (or perhaps more appropriate would be to make it disabled) will take more work. I don’t believe you can easily hijack the button without re-creating the entire input component yourself and building in that logic, but I would be delighted to be proven wrong.
We use a custom snippet that overrides the array Add item button based on an integer based validation rule, It was shared a while back:

https://gist.github.com/Grsmto/cc4db257d05898ca60a9572511fa9bcf .
It requires the code being saved to the project and the part being overridden in the sanity.json

{
      
"implements": "part:@sanity/form-builder/input/array/functions",
      
"path": "./src/components/customArrayFunctions.js"
    
},

We have yet to find any conflicting issues with the snippet so far.
Cool! Thanks for posting this,
user G
. I played around with your snippet to try to implement Sanity UI and get it looking like the buttons in their current version of the studio (full width buttons). Here’s that effort, which is only minimally tested so far:

// Ported from <https://gist.github.com/Grsmto/cc4db257d05898ca60a9572511fa9bcf>

import React from "react";
import {isReferenceSchemaType} from '@sanity/types'
import {AddIcon} from '@sanity/icons'
import {Button, Grid, Menu, MenuButton, MenuItem} from '@sanity/ui'
import {useId} from '@reach/auto-id'

export default function ArrayFunctions(props) {
  const {type, readOnly, children, onCreateValue, onAppendItem, value} = props
  const menuButtonId = useId()

  const insertItem = React.useCallback(
    (itemType) => {
      const item = onCreateValue(itemType)

      onAppendItem(item)
    },
    [onCreateValue, onAppendItem]
  )

  const handleAddBtnClick = React.useCallback(() => {
    insertItem(type.of[0])
  }, [type, insertItem])

  if (readOnly) {
    return null
  }

  const maxLength = type.validation[0]._rules.find(
    (rule) => rule.flag === "max"
  );

  if (maxLength && value && value.length >= maxLength.constraint) {
    return null;
  }

  return (
    <Grid gap={1} style={{gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))'}}>
      {type.of.length === 1 ? (
        <Button icon={AddIcon} mode="ghost" onClick={handleAddBtnClick} text="Add item" />
      ) : (
        <MenuButton
          button={<Button icon={AddIcon} mode="ghost" text="Add item…" />}
          id={menuButtonId || ''}
          menu={
            <Menu>
              {type.of.map((memberDef, i) => {
                const referenceIcon =
                  isReferenceSchemaType(memberDef) &&
                  (<http://memberDef.to|memberDef.to> || []).length === 1 &&
                  <http://memberDef.to[0].icon|memberDef.to[0].icon>

                const icon = memberDef.icon || memberDef.type?.icon || referenceIcon
                return (
                  <MenuItem
                    key={i}
                    text={memberDef.title || memberDef.type?.name}
                    onClick={() => insertItem(memberDef)}
                    icon={icon}
                  />
                )
              })}
            </Menu>
          }
        />
      )}

      {children}
    </Grid>
  );
}
This looks very nice! I’ll give it a try tomorrow, but looks very promising
Thanks
user G
&amp;
user A
! I’ve also been looking for a solution for limiting arrays. I’ve actually been trying to create a custom component this weekend with no success. Unfortunately both of the snippets above don’t work for me either, I get the error ‘onCreateValue is not a function’. Neither onCreateValue or onAppendItem are available as props – I can’t figure out why this is? (Sorry for hijacking this thread with my own problem
user D
, have you got it working?)
Credit to
user M
for the original gist! Unfortunately I've haven't messed around with it too much and only use it in it's current scope, working with the required.max() schema validation rule and not passing anythign else to it.
I love the look of
user A
's implementation with the new Sanity UI as well, though I cant figure out how to get the array item list to appear above (popover) as they did previously when nesting components. At this current time they simply extend the parent container.
I’ve realised my issue with onCreateValue &amp; onAppendItem came from using the component as an inputComponent rather than adding it to the sanity.js file. It’s now working for me! 😀

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?