Avoiding attribute cap with conditional styling customization documents

13 replies
Last updated: Jan 13, 2023
Morning all, and happy new year! I’m wondering if anyone has some guidance for my project. I have a page builder with about 15 - 20 layouts, but want to offer my users finite control over certain styling when necessary. Currently, this is done by attaching a
spacing
object to every layout that grants finite control over margin and padding, but we may want to expand functionality to include other styling as well. As you can imagine, I’m concerned about hitting the attribute cap as the project scales in complexity.
In practice, these are only used about 10% of the time when the user’s design intent isn’t possible with what is already provided with the layout tools. I’m thinking the correct approach would be as follows:

1. Create a new document type called
customization
and hide it from the studio’s view2. For each layout, assign a field with a custom input that allows users to toggle on / off customization for that layout
3. When the customization is toggled on, create a document and reference it
4. Allow the user to modify the document via modal
5. Save changes, or delete the document when it is toggled off.
Is this the right approach, or is there something drastically simpler that could be implemented? Thanks in advance!
AI Update

Happy New Year! Your concern about hitting the attribute cap is valid, but I have good news: your proposed solution is likely more complex than necessary for this use case.

First, let me address the attribute limit concern. Sanity documents do have attribute limits, but these limits are quite generous for most use cases. A spacing object with margin and padding properties on each of your 15-20 layouts shouldn't come close to hitting the cap, even if you expand to include additional styling properties.

The attribute limit applies to the total number of unique keys across your entire document structure. Having nested objects (like your spacing object) is actually a good pattern because:

  • Nested fields count efficiently toward the limit
  • It keeps your schema organized
  • It's easier to manage and understand

My recommendation: Keep it simple and stick with your current approach of adding styling objects directly to each layout. Here's why:

  1. Nested objects are the standard pattern - Having a spacing object (and potentially colors, typography, etc.) directly on your layout schemas is exactly how most Sanity projects handle this
  2. Conditional fields - You can use hidden conditionals to only show these styling fields when needed, keeping the UI clean
  3. Fieldsets - Group your customization fields in collapsible fieldsets so they don't clutter the interface

Here's a cleaner approach than creating separate documents:

{
  name: 'heroLayout',
  type: 'object',
  fields: [
    // Your regular layout fields
    {
      name: 'enableCustomStyling',
      type: 'boolean',
      title: 'Enable Custom Styling',
      initialValue: false
    },
    {
      name: 'customStyles',
      type: 'object',
      title: 'Custom Styles',
      hidden: ({parent}) => !parent?.enableCustomStyling,
      fieldsets: [
        {name: 'spacing', title: 'Spacing', options: {collapsible: true}},
        {name: 'colors', title: 'Colors', options: {collapsible: true}},
      ],
      fields: [
        {
          name: 'marginTop',
          type: 'string',
          fieldset: 'spacing'
        },
        // etc.
      ]
    }
  ]
}

When would your separate document approach make sense?

  • If you're hitting actual attribute limits (you'll get clear errors when trying to save)
  • If you need to reuse the same customization across multiple layouts
  • If you need to version or audit customization changes separately

For your use case where customizations are layout-specific and used only 10% of the time, keeping them as nested objects is simpler, more performant (fewer document lookups), and easier to maintain. The attribute cap shouldn't be a concern unless you're dealing with hundreds of fields per document.

Show original thread
13 replies
Are the layouts themselves documents? And the page-building is happening in-studio to them? And there's no unique ownership of a layout on a per-user basis, correct?
For example, I could go to build a page, I am that page's builder, but the way the page looks on the front-end is dictated by a reusable layout (of which there are 15-20), but I can opt in to customizations that only apply to my page I am building?

So my page + my chosen layout + my customization = front end page?

It feels like the customization should always remain with the thing being customized, and documents and objects are super similar; aside from an ID and and a rev marker the main difference is the two main timestamps, I believe -- for created and updated.

Tracking last updated seems useful, otherwise if the scope of modifications is relatively modest, as you say, I'd almost feel inclined to just modify one style object in place, using spread operations to target just the thins being changed, and, in essence, transacting literal CSS back and forth in JSON.

I don't know what the 'right' approach is or if I have even understood your setup correctly but I'm intrigued myself and am curious to hear other input.

P.S. It makes me think that a
super cool feature for the service here at large may be a capped amount of user-data on a per-dataset basis, since in this case it has a practical application being applied to a document but is also inherently tied to what one person wants. Whether it's a property limit, size limit, etc. to keep it from being abused, it'd be nice to keep it out of the data itself but able to be drawn into and/or inform the project while editing.
user S
sorry for the late reply!
Are the layouts themselves documents?
Nope, the layouts are an array subset of multiple document types.

And the page-building is happening in-studio to them? And there’s no unique ownership of a layout on a per-user basis, correct? For example, I could go to build a page, I am that page’s builder, but the way the page looks on the front-end is dictated by a reusable layout (of which there are 15-20), but I can opt in to customizations that only apply to my page I am building?
Correct. I would clarify that you are opting into customizations at the layout level instead. E.g. a page may consist of 5 layouts, 4 of which are not customized, 1 of which would like some extra top and bottom margin.

So my page + my chosen layout + my customization = front end page?
Correct, again with a slight distinction: My page + my chosen layout(s) + my chosen customization(s) for each layout = front end page

It feels like the customization should always remain with the thing being customized, and documents and objects are super similar; aside from an ID and and a rev marker the main difference is the two main timestamps, I believe -- for created and updated.
Absolutely agree. However, in my testing, it’s becoming VERY apparent that we’ll hit the hard attribute cap very quickly using this method. Thus the thought of using reference documents for customization. Each reference doc would using:
documentId_layoutId
. Instead of having thousands of potential attributes, we would just have
Customizations_Spacing_Margin
and
Customizations_Spacing_Padding
. The alternative is
Layout1_Spacing_Margin
,
Layout2_Spacing_Margin
, etc…

Tracking last updated seems useful, otherwise if the scope of modifications is relatively modest, as you say, I’d almost feel inclined to just modify one style object in place, using spread operations to target just the thins being changed, and, in essence, transacting literal CSS back and forth in JSON.
If I understand you correctly, this unfortunately still requires setting up a complex (and attribute heavy) object in Sanity studio. Unless the intent is to have one text string field with a HIGHLY customized input component. E.g. we would just have a
customStyle
string for each layout and translate user inputs into that string. The difficult part in this is translating the string BACK into a ux-friendly interface for the studio user. Would love to explore more if you have other suggestions here.

I don’t know what the ‘right’ approach is or if I have even understood your setup correctly but I’m intrigued myself and am curious to hear other input.
I’m also curious as to what others are doing to address “edge” cases. Currently, our 15-20 layouts are really just variations of 4 - 5 core layouts, but it still never seems to be enough for the end user. No matter what we always run into common requests like “I want to use that layout, but it want it to be spaced a bit differently”.

P.S. It makes me think that a super cool feature for the service here at large may be a capped amount of user-data on a per-dataset basis, since in this case it has a practical application being applied to a document but is also inherently tied to what one person wants. Whether it’s a property limit, size limit, etc. to keep it from being abused, it’d be nice to keep it out of the data itself but able to be drawn into and/or inform the project while editing.
That would indeed be very interesting to me. More so like attribute metering instead of a hard cap I guess?
Anyway, thanks for your thoughtful response. I’ll update this thread if I make any progress on the challenge myself.
E.g. we would just have a
customStyle
string for each layout and translate user inputs into that string. The difficult part in this is translating the string BACK into a ux-friendly interface for the studio user. Would love to explore more if you have other suggestions here.
Do you mind sharing a snippet of one of them that you might reasonably expect to see? I could benchmark some ideas.
In an ideal world, we’d like the customization to be mapped 1:1 with common css, so for margin and padding customization for a particular layout, you would see schema that includes:

// schema/spacing/index.ts

export default {
  title: 'Spacing',
  name: 'spacing',
  type: 'object',
  ...
  fields: [
    {
      title: 'Margin',
      name: 'margin',
      type: 'margin',
    },
    {
      title: 'Padding',
      name: 'padding',
      type: 'padding',
    },
  ],
}
Where both margin and padding look something like this:

// schema/spacing/margin.ts or schema/spacing/padding.ts

export default {
  title: 'Margin', // or 'Padding'
  name: 'margin', // or 'padding'
  type: 'object',
  ...
  fields: [
    {
      title: 'margin-top', // or 'padding-top', etc...
      name: 'top',
      type: 'spacingDirection',
    },
    {
      title: 'margin-right',
      name: 'right',
      type: 'spacingDirection',
    },
    {
      title: 'margin-bottom',
      name: 'bottom',
      type: 'spacingDirection',
    },
    {
      title: 'margin-left',
      name: 'left',
      type: 'spacingDirection',
    },
  ],
}
And spacing direction woud look something like this:

// schemas/spacing/spacingDirection.ts

export default {
  title: 'Spacing Direction',
  name: 'spacingDirection',
  type: 'object',
  ...
  fields: [
    {
      title: 'Value',
      name: 'value',
      type: 'number',
    },
    {
      title: 'Unit',
      name: 'unit',
      type: 'string',
      options: {
        list: ['px', '%', 'em', 'rm', 'vw', 'vh'],
      },
    },
  ],
}
Right now, this would generate the exact graphql (and a LOT of attributes) necessary for the frontend to do styling.
if we’re outputting a
customStyle
object instead, it would be a single string that would then be consolidated down to:
// example output
customStyle: "margin-top: 0px; margin-bottom: 0x; margin-left: 10em; margin-right: 100px; etc...
This would save on all the fields… but we would need some complex input components to parse this string into mutable values by the studio user (and vice versa).
of course it could also be a possible solution to allow studio users to write custom css to the string field directly, but it doesn’t seem like a plausible solution to design oriented / non-dev oriented studio users
Thanks for that; things are a bit mad-housey today on my current project but I'll carve out some time ASAP to review
no rush, appreciate your help!
I keep going over and over but it seems like an attribute bump either way.
If it were me, and I didn't want to make something really gangly and custom as far as an input component, I'd make the style an array, but each array item you could add would be prescriptive key value pair like property and value.

Which is to say, if I have a dropdown of a property to add (and check against for duplicates), I know how it all works and what makes sense as a field. Padding can't be negative, colors take a hex, etc. And then there's no mis-typing of styles, no bad rules, and you don't need to worry about using packages to translate objects because the input labels and values can be written out to be friendly and parseable and ready to be used all on their own. No conversion necessary.

I can see that if each aspect of one of these layouts had its own away how that could get even busier and drier. And you've already put across that something like this is in place.

I found tooling to translate strings back and forth but especially nowadays with Elementors of the world on the rise they're likely going to want a visual way to do such a visual thing. At that point, the only things I can think of are making a custom input component that's a facsimile of the change (think Developer Tools and the margin/padding boxes at the bottom of the list of styles) or just decide of finite things and forcing people to commit prix fixe style lol
thanks again for looking into this! yes, I agree it’s an attribute bump either way, but i’m still bullish on a one time 100-200 attribute bump vs 100-200 attributes PER layout, which is what will ultimately kill our attribute limit, regardless of plan.
The array key:value is interesting and I can certainly use that for non-visual custom styling, and it will indeed reduce attribute bloat
I think ultimately, a highly custom input component is unavoidable, so I’ve been looking into that as well. I’ll update this thread if I end up with an acceptable demonstration 🙂
Oh okay I didn't realize including them as docs wouldn't also add attributes once they became pulled in by reference. Good to know!
Good luck, friend!

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?