👀 Our most exciting product launch yet 🚀 Join us May 8th for Sanity Connect

Discussion on managing custom styling in a page builder with finite control and hitting attribute cap.

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!
Jan 3, 2023, 2:19 PM
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.
Jan 4, 2023, 3:13 PM
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.
Jan 9, 2023, 2:55 PM
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.
Jan 9, 2023, 4:43 PM
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'],
      },
    },
  ],
}
Jan 10, 2023, 8:55 PM
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).
Jan 10, 2023, 8:59 PM
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
Jan 10, 2023, 9:03 PM
Thanks for that; things are a bit mad-housey today on my current project but I'll carve out some time ASAP to review
Jan 10, 2023, 11:36 PM
no rush, appreciate your help!
Jan 11, 2023, 12:38 PM
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
Jan 12, 2023, 9:52 PM
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.
Jan 13, 2023, 12:42 PM
The array key:value is interesting and I can certainly use that for non-visual custom styling, and it will indeed reduce attribute bloat
Jan 13, 2023, 12:43 PM
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 🙂
Jan 13, 2023, 12:43 PM
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!
Jan 13, 2023, 1:30 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?