Skip to content

Instantly share code, notes, and snippets.

@ajlende
Last active September 23, 2021 19:06
Show Gist options
  • Save ajlende/645f2e08aabff84d7498cfc9ef868f24 to your computer and use it in GitHub Desktop.
Save ajlende/645f2e08aabff84d7498cfc9ef868f24 to your computer and use it in GitHub Desktop.
Filters in WordPress theme.json

Filters in WordPress theme.json

This is an exploration of how theme.json could work in the future—my dream theme.json, if you will. A tour of the new theme.json can be found in theme-v2.json. theme-v1.json is provided for reference to see where properties have moved.

The theme.json files are quite long, so it may be helpful to clone the gist and view the files in a proper editor with code folding.

This gist is more of a living document than a static thing, and there are still a lot of unanswered questions that I'm still working through. I'm pretty happy with the general structure at this point, but there are a few opportunities for things to be added or removed. This is mostly documented in the Unanswered Questions section of this README.

Existing ideas

Excluding templates, I see three main categories of options that are available in theme.json: editor settings, presets, and styles.

Currently the settings property contains two distinctly different types of options: presets and editor UI toggles.

Editor Settings

  • enable/disable a UI feature in the editor
  • Doe NOT produce any CSS

Presets:

  • Generate CSS custom properties (ex. --wp--preset--color--black)
  • Generate utility classes (ex. has-black-color)
  • Provide palettes available from the UI

Styles

  • Generate CSS properties

New ideas

Connecting block.json selectors with theme.json style generation

Duotone block support was added with supports.__experimentalDuotone as a string of the selector to apply the duotone to. This extends that to work for theme.json and block supports that need a different selector than the base block selector.

Custom presets UI & preprocessors

I think a custom property area would be a good place for plugins to integrate with theme.json.

Filters like duotone can be quite complicated—simple strings no longer are enough for css property values. Having configurable preprocessors is a general way to allow a transformation to be done on custom settings.

Structure change

I tried explaining why a structure change was needed in the theme-v2.json tour. The main goal was to fit in selectors and support generic filters, but in doing so I found a different structure for the existing settings that works better for the new selectors idea. And once I was changing the structure for that, I ended up thinking about the structure as a whole.

In my proposed structure change, I optimized for consistency first and then flexibility second. I didn't try to optimize for "ease of use" because that's not very well defined. I think consistency contributes to "ease of use", but there may be a better metric for optimization than consistency.

Consistency, in this context, means sets of properties in sections of the JSON document are identical to sets of properties in other sections of the JSON document and structures in the JSON document mirror existing CSS/HTML ideas. Flexibility is forward-thinking for ideas that have been discussed as future improvements to global styles.

We may end up just fitting selectors in to the existing structure

Unanswered Questions

theme.json

  • {customPresets,presets}.*
    • Should all the values be arrays of objects with label, slug, and value properties?
    • What should an empty array mean? Should we allow empty arrays?
    • If we don't use a preprocessor, would it be identical to the same thing in customProperties?
  • customPresets.preprocessors
    • If the structure isn't an array, how should arguments be passed?
    • Should an array just be required for preprocessors?
  • customPresets.root.dropShadow
    • How can we change the color based on the background? There's probably some intermediate CSS var that we can set that is controlled by background?
    • What sort of interface will we need to combine multiple custom duotones and drop shadows?
  • {styles,customProperties}.selectors
    • Should we allow generic CSS selectors to be created in theme.json? If so, how?
    • Would we be able to name them and use them inside blocks like extending the block.json's selectors property?
    • Or (and this is probably simpler and more consistent) do we just allow generic CSS selectors as the keys and keep the values the same, much like elements?

block.json

  • selectors
    • How do existing usages of __experimentalSelector work with the new selectors format?
    • core/code __experimentalSelector produces incorrect results when using with theme.json.

Related Issues/PRs

{
"apiVersion": 2,
"name": "core/image",
"title": "Image",
"category": "media",
"usesContext": ["allowResize", "imageCrop"],
"description": "Insert an image to make a visual statement.",
"keywords": ["img", "photo", "picture"],
"textdomain": "default",
"attributes": {
/* ... */
},
"selectors": {
// Should root be overridable? I think no, but we could make root a
// reserved key if we want to.
"root": ".wp-block-image",
// We could require block supports to have a matching selector if
// needed to make it easy to identify all the places it is used.
// If we allow overriding the root selector, I think we should
// include the block class here, but if not, it could be scoped
// automatically like `__experimentalDuotone`.
"duotone": ".wp-block-image img",
// Additional, generic selectors could be added for themers, although
// you'll have to be careful with not accidentally overriding the filter
// if you have two identical selectors with different names.
"media": ".wp-block-image img"
},
"supports": {
"anchor": true,
"filter": {
"duotone": true,
// Maybe a future filter could be the drop-shadow filter.
"dropShadow": true
},
"color": {
"text": false,
"background": false
},
"__experimentalBorder": {
"radius": true
}
},
"styles": [
{
// Could rename `name` → `slug` for consistency with theme.json
"name": "default",
"label": "Default",
"isDefault": true
},
{ "name": "rounded", "label": "Rounded" }
],
"editorStyle": "wp-block-image-editor",
"style": "wp-block-image"
}
<?php
/**
* Would come from a plugin.
*/
function my_font_filter_preprocessor( $preset ) {
// TODO: What should $preset look like?
// See https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/a66341ae-9277-4e93-b50b-c99e91a3f083/weathered-svg.html.
}
/**
* Would come from a plugin.
*/
function my_fancy_gradient_preprocessor( $preset ) {
// TODO: What should $preset look like?
}
/**
* Simple wrapper for gutenberg_get_duotone_filter_property.
* See https://github.com/WordPress/gutenberg/pull/34667.
*/
function gutenberg_duotone_preprocessor( $preset ) {
return gutenberg_get_duotone_filter_property( $preset['slug'], $preset['value'] );
}
{
// TODO: Waiting to fill this out until the example theme.json is more complete.
}
{
"$schema": "https://json.schemastore.org/theme-v1.json",
"version": 1,
"settings": {
"border": {
"customColor": true,
"customRadius": true,
"customStyle": true,
"customWidth": true
},
"color": {
"background": true,
"custom": true,
"customDuotone": true,
"customGradient": true,
"duotone": [
{
"name": "High Contrast",
"slug": "high-contrast",
"colors": ["#002b36", "#fdf6e3"]
},
{
"name": "Base Dark",
"slug": "base-dark",
"colors": ["#002b36", "#839496"]
},
{
"name": "Base Light",
"slug": "base-light",
"colors": ["#657b83", "#fdf6e3"]
},
{
"name": "Yellow",
"slug": "yellow",
"colors": ["#002b36", "#b58900"]
},
{
"name": "Orange",
"slug": "orange",
"colors": ["#cb4b16", "#fdf6e3"]
},
{
"name": "Red",
"slug": "red",
"colors": ["#dc322f", "#fdf6e3"]
},
{
"name": "Magenta",
"slug": "magenta",
"colors": ["#d33682", "#fdf6e3"]
},
{
"name": "Violet",
"slug": "violet",
"colors": ["#6c71c4", "#fdf6e3"]
},
{
"name": "Blue",
"slug": "blue",
"colors": ["#002b36", "#268bd2"]
},
{
"name": "Cyan",
"slug": "cyan",
"colors": ["#002b36", "#2aa198"]
},
{
"name": "Green",
"slug": "green",
"colors": ["#002b36", "#859900"]
}
],
"gradients": [
{
"name": "Base",
"slug": "base",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--base-3))"
},
{
"name": "Base Dark",
"slug": "base-dark",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--base-0))"
},
{
"name": "Base Light",
"slug": "base-light",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-00),var(--wp--preset--color--base-3))"
},
{
"name": "Yellow",
"slug": "yellow",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--yellow))"
},
{
"name": "Orange",
"slug": "orange",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--orange),var(--wp--preset--color--base-3))"
},
{
"name": "Red",
"slug": "red",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--red),var(--wp--preset--color--base-3))"
},
{
"name": "Magenta",
"slug": "magenta",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--magenta),var(--wp--preset--color--base-3))"
},
{
"name": "Violet",
"slug": "violet",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--violet),var(--wp--preset--color--base-3))"
},
{
"name": "Blue",
"slug": "blue",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--blue))"
},
{
"name": "Cyan",
"slug": "cyan",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--cyan))"
},
{
"name": "Green",
"slug": "green",
"gradient": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--green))"
}
],
"link": true,
"palette": [
{
"name": "Base03",
"slug": "base-03",
"color": "#002b36"
},
{
"name": "Base02",
"slug": "base-02",
"color": "#073642"
},
{
"name": "Base01",
"slug": "base-01",
"color": "#586e75"
},
{
"name": "Base00",
"slug": "base-00",
"color": "#657b83"
},
{
"name": "Base0",
"slug": "base-0",
"color": "#839496"
},
{
"name": "Base1",
"slug": "base-1",
"color": "#93a1a1"
},
{
"name": "Base2",
"slug": "base-2",
"color": "#eee8d5"
},
{
"name": "Base3",
"slug": "base-3",
"color": "#fdf6e3"
},
{
"name": "Yellow",
"slug": "yellow",
"color": "#b58900"
},
{
"name": "Orange",
"slug": "orange",
"color": "#cb4b16"
},
{
"name": "Red",
"slug": "red",
"color": "#dc322f"
},
{
"name": "Magenta",
"slug": "magenta",
"color": "#d33682"
},
{
"name": "Violet",
"slug": "violet",
"color": "#6c71c4"
},
{
"name": "Blue",
"slug": "blue",
"color": "#268bd2"
},
{
"name": "Cyan",
"slug": "cyan",
"color": "#2aa198"
},
{
"name": "Green",
"slug": "green",
"color": "#859900"
}
],
"text": true
},
"layout": {
"contentSize": "620px",
"wideSize": "calc( 2 * 620px + var(--wp--style--block-gap) )"
},
"spacing": {
"blockGap": true,
"customMargin": true,
"customPadding": true,
"units": ["px", "em", "rem", "%", "vw"]
},
"typography": {
"customFontSize": true,
"customFontStyle": true,
"customFontWeight": true,
"customLetterSpacing": true,
"customLineHeight": true,
"customTextDecorations": true,
"customTextTransforms": true,
"dropCap": true,
"fontSizes": [
{
"name": "Extra Small",
"slug": "x-small",
"size": "10px"
},
{
"name": "Small",
"slug": "small",
"size": "13px"
},
{
"name": "Normal",
"slug": "normal",
"size": "16px"
},
{
"name": "Medium",
"slug": "medium",
"size": "20px"
},
{
"name": "Large",
"slug": "large",
"size": "36px"
},
{
"name": "Extra Large",
"slug": "x-large",
"size": "42px"
}
],
"fontFamilies": [
{
"name": "System",
"slug": "system",
"fontFamily": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif"
},
{
"name": "Serif",
"slug": "serif",
"fontFamily": "Georgia,\"Times\",\"Times New Roman\",serif"
},
{
"name": "Monospace",
"slug": "monospace",
"fontFamily": "Consolas,monaco,monospace"
}
]
},
"custom": {
"blockGap": "42px",
"color": {
"foreground": "var(--wp--preset--color--base-00)",
"background": "var(--wp--preset--color--base-3)",
"primary": "var(--wp--preset--color--base-01)",
"secondary": "var(--wp--preset--color--base-1)",
"selection": "var(--wp--preset--color--base-2)",
"accent": "var(--wp--preset--color--yellow)",
"link": "var(--wp--preset--color--blue)"
},
"heading": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": 400,
"lineHeight": 1.125
},
"margin": {
"horizontal": "42px",
"vertical": "42px"
},
"a": {
"color": "var(--wp--preset--color--blue)"
},
"body": {
"background": "var(--wp--custom--color--background)",
"color": "var(--wp--custom--color--foreground)",
"lineHeight": 1.6,
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--normal)",
"paddingTop": "var(--wp--custom--margin--vertical)",
"paddingBottom": "var(--wp--custom--margin--vertical)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"paddingRight": "var(--wp--custom--margin--horizontal)"
},
"button": {
"borderColor": "var(--wp--custom--color--primary)",
"borderRadius": "4px",
"borderStyle": "solid",
"borderWidth": "2px",
"background": "var(--wp--custom--color--primary)",
"color": "var(--wp--custom--color--background)",
"paddingTop": "0.667em",
"paddingBottom": "0.667em",
"paddingLeft": "1.333em",
"paddingRight": "1.333em",
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--normal)",
"fontWeight": "normal",
"lineHeight": 2
},
"code": {
"borderColor": "var(--wp--custom--color--primary)",
"borderRadius": "0px",
"borderStyle": "solid",
"borderWidth": "2px",
"color": "var(--wp--custom--color--foreground)",
"background": "var(--wp--custom--color--background)",
"paddingTop": "var(--wp--custom--margin--vertical)",
"paddingBottom": "var(--wp--custom--margin--vertical)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"paddingRight": "var(--wp--custom--margin--horizontal)",
"fontFamily": "var(--wp--preset--font-family--monospace)"
},
"h1": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--x-large)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h2": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--large)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h3": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--medium)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h4": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--normal)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h5": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--normal)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h6": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--normal)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"list": {
"paddingLeft": "calc( 2 * var(--wp--custom--margin--horizontal) )"
},
"navigation": {
"fontSize": "var(--wp--preset--font-size--normal)"
},
"post-author": {
"fontWeight": "normal"
},
"post-comment": {
"fontSize": "var(--wp--preset--font-size--normal)",
"lineHeight": "var(--wp--custom--body--line-height)"
},
"post-content": {
"paddingLeft": "20px",
"paddingRight": "20px"
},
"post-date": {
"color": "var(--wp--custom--color--foreground)",
"fontSize": "var(--wp--preset--font-size--small)"
},
"post-date_a": {
"color": "var(--wp--custom--color--accent)"
},
"post-title": {
"fontFamily": "var(--wp--custom--h1--font-family)",
"fontSize": "var(--wp--custom--h1--font-size)",
"lineHeight": "var(--wp--custom--h1--line-height)"
},
"pullquote": {
"borderWidth": "1px 0",
"borderStyle": "solid",
"paddingTop": "var(--wp--custom--margin--horizontal)",
"paddingBottom": "var(--wp--custom--margin--horizontal)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"paddingRight": "var(--wp--custom--margin--horizontal)",
"fontStyle": "italic",
"fontSize": "var(--wp--preset--font-size--huge)"
},
"quote": {
"borderWidth": "0 0 0 1px",
"borderStyle": "solid",
"borderColor": "var(--wp--custom--color--primary)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"fontSize": "var(--wp--preset--font-size--normal)",
"fontStyle": "normal"
},
"separator": {
"borderWidth": "0 0 1px 0",
"borderStyle": "solid",
"borderColor": "currentColor",
"color": "var(--wp--custom--color--foreground)"
},
"site-title": {
"fontSize": "72px",
"fontWeight": 700
}
},
"blocks": {
"core/pullquote": {
"border": {
"customWidth": false,
"customStyle": false,
"customRadius": false
}
}
}
},
"styles": {
"color": {
"background": "var(--wp--custom--body--background)",
"text": "var(--wp--custom--body--color)"
},
"spacing": {
"blockGap": "var(--wp--custom--block-gap)",
"padding": {
"top": "var(--wp--custom--body--padding-top)",
"bottom": "var(--wp--custom--body--padding-bottom)",
"left": "var(--wp--custom--body--padding-left)",
"right": "var(--wp--custom--body--padding-right)"
}
},
"typography": {
"lineHeight": "var(--wp--custom--body--line-height)",
"fontFamily": "var(--wp--custom--body--font-family)",
"fontSize": "var(--wp--custom--body--font-size)"
},
"blocks": {
"core/button": {
"border": {
"radius": "var(--wp--custom--button--border-radius)",
"width": "var(--wp--custom--button--border-width)",
"style": "var(--wp--custom--button--border-style)",
"color": "var(--wp--custom--button--border-color)"
},
"color": {
"background": "var(--wp--custom--button--background)",
"text": "var(--wp--custom--button--color)"
},
"spacing": {
"padding": {
"top": "var(--wp--custom--button--padding-top)",
"bottom": "var(--wp--custom--button--padding-bottom)",
"left": "var(--wp--custom--button--padding-left)",
"right": "var(--wp--custom--button--padding-right)"
}
},
"typography": {
"fontFamily": "var(--wp--custom--button--font-family)",
"fontSize": "var(--wp--custom--button--font-size)",
"fontWeight": "var(--wp--custom--button--font-weight)",
"lineHeight": "var(--wp--custom--button--line-height)"
}
},
"core/code": {
"border": {
"radius": "var(--wp--custom--code--border-radius)",
"width": "var(--wp--custom--code--border-width)",
"style": "var(--wp--custom--code--border-style)",
"color": "var(--wp--custom--code--border-color)"
},
"color": {
"text": "var(--wp--custom--code--color)",
"background": "var(--wp--custom--code--background)"
},
"spacing": {
"padding": {
"top": "var(--wp--custom--code--padding-top)",
"bottom": "var(--wp--custom--code--padding-bottom)",
"left": "var(--wp--custom--code--padding-left)",
"right": "var(--wp--custom--code--padding-right)"
}
},
"typography": {
"fontFamily": "var(--wp--custom--code--font-family)"
}
},
"core/image": {
"color": {
"duotone": "var(--wp--custom--image--duotone)"
}
},
"core/list": {
"spacing": {
"padding": {
"left": "var(--wp--custom--list--padding-left)"
}
}
},
"core/navigation": {
"typography": {
"fontSize": "var(--wp--custom--navigation--font-size)"
}
},
"core/post-author": {
"typography": {
"fontWeight": "var(--wp--custom--post-author--font-weight)"
}
},
"core/post-comment": {
"typography": {
"fontSize": "var(--wp--custom--post-comment--font-size)",
"lineHeight": "var(--wp--custom--post-comment--line-height)"
}
},
"core/post-content": {
"spacing": {
"padding": {
"left": "var(--wp--custom--post-comment--padding-left)",
"right": "var(--wp--custom--post-comment--padding-right)"
}
}
},
"core/post-date": {
"color": {
"text": "var(--wp--custom--post-date--color)"
},
"typography": {
"fontSize": "var(--wp--custom--post-date--font-size)"
},
"elements": {
"link": {
"color": {
"text": "var(--wp--custom--post-date_a--color)"
}
}
}
},
"core/post-title": {
"typography": {
"fontFamily": "var(--wp--custom--post-title--font-family)",
"fontSize": "var(--wp--custom--post-title--font-size)",
"lineHeight": "var(--wp--custom--post-title--line-height)"
}
},
"core/pullquote": {
"border": {
"width": "var(--wp--custom--pullquote--border-width)",
"style": "var(--wp--custom--pullquote--border-style)"
},
"spacing": {
"padding": {
"top": "var(--wp--custom--pullquote--padding-top)",
"bottom": "var(--wp--custom--pullquote--padding-bottom)",
"left": "var(--wp--custom--pullquote--padding-left)",
"right": "var(--wp--custom--pullquote--padding-right)"
}
},
"typography": {
"fontStyle": "var(--wp--custom--pullquote--border-style)",
"fontSize": "var(--wp--custom--pullquote--border-style)"
}
},
"core/quote": {
"border": {
"width": "var(--wp--custom--quote--border-width)",
"style": "var(--wp--custom--quote--border-style)",
"color": "var(--wp--custom--quote--border-color)"
},
"spacing": {
"padding": {
"left": "var(--wp--custom--quote--padding-left)"
}
},
"typography": {
"fontSize": "var(--wp--custom--quote--font-size)",
"fontStyle": "var(--wp--custom--quote--font-style)"
}
},
"core/separator": {
"border": {
"width": "var(--wp--custom--separator--border-width)",
"style": "var(--wp--custom--separator--border-style)",
"color": "var(--wp--custom--separator--border-color)"
},
"color": {
"text": "var(--wp--custom--separator--color)"
}
},
"core/site-title": {
"typography": {
"fontSize": "var(--wp--custom--site-title--font-size)",
"fontWeight": "var(--wp--custom--site-title--font-weight)"
}
}
},
"elements": {
"h1": {
"typography": {
"fontFamily": "var(--wp--custom--h1--font-family)",
"fontWeight": "var(--wp--custom--h1--font-weight)",
"lineHeight": "var(--wp--custom--h1--line-height)",
"fontSize": "var(--wp--custom--h1--font-size)"
},
"spacing": {
"margin": {
"top": "var(--wp--custom--h1--margin-top)",
"bottom": "var(--wp--custom--h1--margin-bottom)"
}
}
},
"h2": {
"typography": {
"fontFamily": "var(--wp--custom--h2--font-family)",
"fontWeight": "var(--wp--custom--h2--font-weight)",
"lineHeight": "var(--wp--custom--h2--line-height)",
"fontSize": "var(--wp--custom--h2--font-size)"
},
"spacing": {
"margin": {
"top": "var(--wp--custom--h2--margin-top)",
"bottom": "var(--wp--custom--h2--margin-bottom)"
}
}
},
"h3": {
"typography": {
"fontFamily": "var(--wp--custom--h3--font-family)",
"fontWeight": "var(--wp--custom--h3--font-weight)",
"lineHeight": "var(--wp--custom--h3--line-height)",
"fontSize": "var(--wp--custom--h3--font-size)"
},
"spacing": {
"margin": {
"top": "var(--wp--custom--h3--margin-top)",
"bottom": "var(--wp--custom--h3--margin-bottom)"
}
}
},
"h4": {
"typography": {
"fontFamily": "var(--wp--custom--h4--font-family)",
"fontWeight": "var(--wp--custom--h4--font-weight)",
"lineHeight": "var(--wp--custom--h4--line-height)",
"fontSize": "var(--wp--custom--h4--font-size)"
},
"spacing": {
"margin": {
"top": "var(--wp--custom--h4--margin-top)",
"bottom": "var(--wp--custom--h4--margin-bottom)"
}
}
},
"h5": {
"typography": {
"fontFamily": "var(--wp--custom--h5--font-family)",
"fontWeight": "var(--wp--custom--h5--font-weight)",
"lineHeight": "var(--wp--custom--h5--line-height)",
"fontSize": "var(--wp--custom--h5--font-size)"
},
"spacing": {
"margin": {
"top": "var(--wp--custom--h5--margin-top)",
"bottom": "var(--wp--custom--h5--margin-bottom)"
}
}
},
"h6": {
"typography": {
"fontFamily": "var(--wp--custom--h6--font-family)",
"fontWeight": "var(--wp--custom--h6--font-weight)",
"lineHeight": "var(--wp--custom--h6--line-height)",
"fontSize": "var(--wp--custom--h6--font-size)"
},
"spacing": {
"margin": {
"top": "var(--wp--custom--h6--margin-top)",
"bottom": "var(--wp--custom--h6--margin-bottom)"
}
}
},
"link": {
"color": {
"text": "var(--wp--custom--a--color)"
}
}
}
}
}
{
// Welcome to the tour of my dream theme.json. This could be version 2.
"version": 2,
// Settings are now reserved for setting editor UI features to make a
// distinction between the settings that generate presets and the ones
// that don't. Also can solve the "empty array to disable is confusing"
// problem that `duotone` and `gradients` currently have.
"settings": {
// Applies to all blocks. Having a `root` property is good for consistency
// because the contents are identical to the property under blocks. And
// if we ever want to create a setting called `blocks` for whatever reason,
// we can do that now. It will make more sense as you go.
"root": {
// Moved from `settings.border`.
"customBorderColor": true,
"customBorderRadius": true,
"customBorderStyle": true,
"customBorderWidth": true,
// Moved from `settings.color`.
"backgroundColor": true,
"colorPalette": true, // New, enable/disable color palette.
"customColor": true,
"customDuotone": true,
"customGradient": true,
"duotonePalette": true, // New, enable/disable duotone palette.
"gradientPalette": true, // New, enable/disable gradients palette.
"linkColor": true,
"textColor": true,
// Moved from `settings.spacing`.
"blockGap": true,
"customMargin": true,
"customPadding": true,
"spacingUnits": ["px", "em", "rem", "%", "vw"],
// Moved from `settings.typography`.
"customFontSize": true,
"customFontStyle": true,
"customFontWeight": true,
"customLetterSpacing": true,
"customLineHeight": true,
"customTextDecorations": true,
"customTextTransforms": true,
"dropCap": true
// Flattened structure so UI changes to the groupings in the global
// styles editor don't require changing the theme.json structure to
// match. Maybe they could still be nested, but I don't think the
// nesting is really necessary. There may be an opportunity to add
// some nesting to match block supports here (and maybe that was the
// intention before), but if we add that nesting, putting all the
// block supports related features under `supports` could make that
// more clear.
},
// Overridable on a per-block basis.
"blocks": {
"core/pullquote": {
// Having the `root` property is helpful here for consistency
// with the other blocks overrides in styles, but I can't think
// of any other properties that we would want to add except for
// some recursive blocks within blocks nesting.
"root": {
"customBorderWidth": false,
"customBorderStyle": false,
"customBorderRadius": false
}
}
}
},
// Presets use the --wp--preset-- prefix so the connection between the
// generated CSS and theme.json is clearer.
"presets": {
// Maintaining `root` here for consistency and for the reasons stated
// above. Additionally, the CSS variables may be be nested under the
// `:root` pseudo-class instead of `body` where they currently are.
"root": {
// Moved from `styles.blockGap`. Here it would generate
// --wp--preset--block-gap instead of the --wp--style prefix.
"blockGap": "42px",
// Moved from `settings.layout`.
"layout": {
// Generates --wp--preset--layout--content-size.
"contentSize": "620px",
// Generates --wp--preset--layout--wide-size.
"wideSize": "calc( 2 * var(--wp--preset--layout--content-size) + var(--wp--preset--block-gap) )"
},
// Moved from `settings.color.duotone`.
"duotone": [
{
"name": "High Contrast",
"slug": "high-contrast",
"value": ["#002b36", "#fdf6e3"]
},
{
"name": "Base Dark",
"slug": "base-dark",
"value": ["#002b36", "#839496"]
},
{
"name": "Base Light",
"slug": "base-light",
"value": ["#657b83", "#fdf6e3"]
},
{
"name": "Yellow",
"slug": "yellow",
"value": ["#002b36", "#b58900"]
},
{
"name": "Orange",
"slug": "orange",
"value": ["#cb4b16", "#fdf6e3"]
},
{
"name": "Red",
"slug": "red",
"value": ["#dc322f", "#fdf6e3"]
},
{
"name": "Magenta",
"slug": "magenta",
"value": ["#d33682", "#fdf6e3"]
},
{
"name": "Violet",
"slug": "violet",
"value": ["#6c71c4", "#fdf6e3"]
},
{
"name": "Blue",
"slug": "blue",
"value": ["#002b36", "#268bd2"]
},
{
"name": "Cyan",
"slug": "cyan",
"value": ["#002b36", "#2aa198"]
},
{
"name": "Green",
"slug": "green",
"value": ["#002b36", "#859900"]
}
],
// Moved from `settings.color.gradients`.
"gradient": [
{
"name": "Base",
"slug": "base",
"value": "linear-gradient( 135deg, var(--wp--preset--color--base-03), var(--wp--preset--color--base-3) )"
},
{
"name": "Base Dark",
"slug": "base-dark",
"value": "linear-gradient( 135deg, var(--wp--preset--color--base-03), var(--wp--preset--color--base-0) )"
},
{
"name": "Base Light",
"slug": "base-light",
"value": "linear-gradient( 135deg, var(--wp--preset--color--base-00), var(--wp--preset--color--base-3) )"
},
{
"name": "Yellow",
"slug": "yellow",
"value": "linear-gradient( 135deg, var(--wp--preset--color--base-03), var(--wp--preset--color--yellow) )"
},
{
"name": "Orange",
"slug": "orange",
"value": "linear-gradient( 135deg, var(--wp--preset--color--orange), var(--wp--preset--color--base-3) )"
},
{
"name": "Red",
"slug": "red",
"value": "linear-gradient( 135deg, var(--wp--preset--color--red), var(--wp--preset--color--base-3) )"
},
{
"name": "Magenta",
"slug": "magenta",
"value": "linear-gradient( 135deg, var(--wp--preset--color--magenta), var(--wp--preset--color--base-3) )"
},
{
"name": "Violet",
"slug": "violet",
"value": "linear-gradient( 135deg, var(--wp--preset--color--violet), var(--wp--preset--color--base-3) )"
},
{
"name": "Blue",
"slug": "blue",
"value": "linear-gradient( 135deg, var(--wp--preset--color--base-03), var(--wp--preset--color--blue) )"
},
{
"name": "Cyan",
"slug": "cyan",
"value": "linear-gradient(135deg,var(--wp--preset--color--base-03),var(--wp--preset--color--cyan) )"
},
{
"name": "Green",
"slug": "green",
"value": "linear-gradient( 135deg, var(--wp--preset--color--base-03), var(--wp--preset--color--green) )"
}
],
// Moved from `settings.color.palette`.
"color": [
{
"name": "Base03",
"slug": "base-03",
"value": "#002b36"
},
{
"name": "Base02",
"slug": "base-02",
"value": "#073642"
},
{
"name": "Base01",
"slug": "base-01",
"value": "#586e75"
},
{
"name": "Base00",
"slug": "base-00",
"value": "#657b83"
},
{
"name": "Base0",
"slug": "base-0",
"value": "#839496"
},
{
"name": "Base1",
"slug": "base-1",
"value": "#93a1a1"
},
{
"name": "Base2",
"slug": "base-2",
"value": "#eee8d5"
},
{
"name": "Base3",
"slug": "base-3",
"value": "#fdf6e3"
},
{
"name": "Yellow",
"slug": "yellow",
"value": "#b58900"
},
{
"name": "Orange",
"slug": "orange",
"value": "#cb4b16"
},
{
"name": "Red",
"slug": "red",
"value": "#dc322f"
},
{
"name": "Magenta",
"slug": "magenta",
"value": "#d33682"
},
{
"name": "Violet",
"slug": "violet",
"value": "#6c71c4"
},
{
"name": "Blue",
"slug": "blue",
"value": "#268bd2"
},
{
"name": "Cyan",
"slug": "cyan",
"value": "#2aa198"
},
{
"name": "Green",
"slug": "green",
"value": "#859900"
}
],
// Moved from `settings.typography.fontSizes`.
"fontSize": [
{
"name": "Extra Small",
"slug": "x-small",
"value": "10px"
},
{
"name": "Small",
"slug": "small",
"value": "13px"
},
{
"name": "Normal",
"slug": "normal",
"value": "16px"
},
{
"name": "Medium",
"slug": "medium",
"value": "20px"
},
{
"name": "Large",
"slug": "large",
"value": "36px"
},
{
"name": "Extra Large",
"slug": "x-large",
"value": "42px"
}
],
// Moved from `settings.typography.fontFamilies`.
"fontFamily": [
{
"name": "System",
"slug": "system",
"value": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif"
},
{
"name": "Serif",
"slug": "serif",
"value": "Georgia,\"Times\",\"Times New Roman\",serif"
},
{
"name": "Monospace",
"slug": "monospace",
"value": "Consolas,monaco,monospace"
}
]
// Naming and nesting changed so the nesting matches up with the
// --wp--preset--<feature>--<slug> properties for consistency.
},
// Again, overridable. See `settings.blocks`.
"blocks": {
"core/image": {
// Again, consistency. See `settings.blocks['core/image'].root`.
"root": {
"duotone": [
{
"name": "Sepia",
"slug": "sepia",
"value": ["#7c1d00", "#f8daaa"]
}
]
}
}
}
},
// I imagine customPresets kind of somewhere between the new presets and the
// old settings.custom. Could be useful for extending theme.json in plugins.
"customPresets": {
// Again, we could use `:root` pseudo-selector if we want.
// See `presets.root` above.
"root": {
// Create custom duotone presets without adding them to the duotone UI.
"customDuotone": [
{
"name": "Black and white",
"slug": "black-and-white",
"value": ["#000000", "#ffffff"]
}
],
// Can be accessed from a plugin that provides font filter presets
// and a fancy UI in the form of block supports.
"fontFilter": [
{
"name": "Grunge",
"slug": "grunge",
"value": {
"extrudeColor": "var(--wp--preset--color--white)"
}
}
],
// For illustrating other structures besides arrays that may or may
// not be useful.
"fancyGradient": {
"checkerboard": ["#000000", "#ffffff"],
"stripes": {
"angle": "45deg",
"colors": ["#000000", "#ffffff"]
}
},
// If this didn't have a preprocessor, would it be identical to the
// same thing in customProperties?
"fancyLayout": {
"angle": "5deg"
}
},
// Again, overridable. See `settings.blocks`.
"blocks": {
"core/image": {
// Again, consistency. See `settings.blocks['core/image'].root`.
"root": {
"customDuotone": [
// This example is used in `styles.blocks['core/image']`.
{
"name": "White and black",
"slug": "white-and-black",
"value": ["#ffffff", "#000000"]
}
]
}
}
},
// The preprocessors key would be unique here. The keys map from a
// feature to a function that processes the final output, like duotone.
// The preprocessor may also directly render an additional stylesheet
// that provides utility classes like `has-<slug>-color` for colors.
// If a plugin requires preprocessing for their presets, their function
// would be added here.
"preprocessors": {
"customDuotone": "gutenberg_duotone_preprocessor",
"fontFilter": "my_font_filter_preprocessor",
// If the structure isn't an array, how should arguments be passed?
// Should an array just be required for preprocessors?
"fancyGradient": "my_fancy_gradient_preprocessor"
}
},
// If we have plugin support via customProperties, we may also want a place
// for plugins to toggle editor user interface features.
"customSettings": {},
// This is nearly identical to `settings.custom` from version 1.
"customProperties": {
// Again, we could use `:root` pseudo-selector if we want.
// See `presets.root` above.
"root": {
"color": {
"foreground": "var(--wp--preset--color--base-00)",
"background": "var(--wp--preset--color--base-3)",
"primary": "var(--wp--preset--color--base-01)",
"secondary": "var(--wp--preset--color--base-1)",
"selection": "var(--wp--preset--color--base-2)",
"accent": "var(--wp--preset--color--yellow)",
"link": "var(--wp--preset--color--blue)"
},
"heading": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": 400,
"lineHeight": 1.125
},
"margin": {
"horizontal": "42px",
"vertical": "42px"
}
},
// Overriding or defining custom properties could be useful in elements.
"elements": {
// Explanation for why we could use `a` instead of `link` in the
// `styles` section below.
"a": {
"foreground": "var(--wp--preset--color--blue)"
}
},
// Overriding or defining custom properties could be useful in blocks too.
"blocks": {
// For example, we could use a dark mode for code blocks, complete
// with a properly contrasting text selection.
"core/code": {
// Having the `root` property here is actually useful because we
// are starting to have use-cases for other properties.
"root": {
"color": {
"foreground": "var(--wp--preset--color--base-00)",
"background": "var(--wp--preset--color--base-3)",
"primary": "var(--wp--preset--color--base-01)",
"secondary": "var(--wp--preset--color--base-1)",
"selection": "var(--wp--preset--color--base-2)"
}
},
// Maybe we want to go a step even further and override custom
// properties for code elements, but only within the code block.
"elements": {
"code": {
"color": {
"background": "var(--wp--preset--color--base-3)"
}
}
},
// A selectors key could also be added here. They're described
// below in the `styles.blocks['core/image']` section.
"selectors": {}
}
}
},
// Serving the same purpose as `styles` in version 1, but now more CSS-like.
// Camel case properties just like CSSStyleDeclaration to keep things consistent.
// See https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration#css_properties.
// Flattening the nesting also serves the same purpose as in `settings`. And
// naming things like CSS separates it from presets and customProperties as
// a different thing. In this case, the lack of consistency between them is
// a feature because they behave differently.
"styles": {
// In this one scenario, `root` may actually mean `body`, so it
// might make sense to disallow `root` here and put this under
// `elements.body` instead. It's nice having the consistency of
// always seeing `root` at the top as a sort of guide for what
// sort of things this secion controls, but it isn't consistent
// with CSS.
"root": {
"background": "var(--wp--custom--color--background)",
"color": "var(--wp--custom--color--foreground)",
"lineHeight": "1.6",
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--normal)",
"paddingTop": "var(--wp--custom--margin--vertical)",
"paddingBottom": "var(--wp--custom--margin--vertical)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"paddingRight": "var(--wp--custom--margin--horizontal)"
},
// Apply styles to elements! The properties here mirror those in the
// theme-v1.json file, so there are a lot of them. Feel free to skip to
// `blocks` which has some interesting bits.
"elements": {
// Could be aliased to `link`, but I like the consistency with CSS.
"a": {
// Could be aliased to `textColor`, but I like the consistency with CSS.
"color": "var(--wp--preset--color--blue)"
},
"h1": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--x-large)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)",
// This is an instance of using filters without `var()`.
"filter": "drop-shadow( 5px 5px 8px rgba( 0, 0, 0, 0.5 ) )"
},
"h2": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--large)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h3": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--medium)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h4": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--normal)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h5": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--normal)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
},
"h6": {
"fontFamily": "var(--wp--preset--font-family--system)",
"fontWeight": "var(--wp--custom--heading--font-weight)",
"lineHeight": "var(--wp--custom--heading--line-height)",
"fontSize": "var(--wp--preset--font-size--normal)",
"marginTop": "var(--wp--custom--margin--vertical)",
"marginBottom": "var(--wp--custom--margin--vertical)"
}
},
// Apply styles to blocks! The properties here mirror those in the
// theme-v1.json file, so there are a lot of them. Skip ahead to
// `core/cover` for how duotone `selectors` work, and to `core/post-date`
// for an example with `elements`.
"blocks": {
"core/button": {
"root": {
"borderColor": "var(--wp--custom--color--primary)",
"borderRadius": "4px",
"borderStyle": "solid",
"borderWidth": "2px",
"background": "var(--wp--custom--color--primary)",
"color": "var(--wp--custom--color--background)",
"paddingTop": "0.667em",
"paddingBottom": "0.667em",
"paddingLeft": "1.333em",
"paddingRight": "1.333em",
"fontFamily": "var(--wp--preset--font-family--system)",
"fontSize": "var(--wp--preset--font-size--normal)",
"fontWeight": "normal",
"lineHeight": 2
}
},
"core/code": {
"root": {
"borderColor": "var(--wp--custom--color--primary)",
"borderRadius": "0px",
"borderStyle": "solid",
"borderWidth": "2px",
"color": "var(--wp--custom--color--foreground)",
"background": "var(--wp--custom--color--background)",
"paddingTop": "var(--wp--custom--margin--vertical)",
"paddingBottom": "var(--wp--custom--margin--vertical)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"paddingRight": "var(--wp--custom--margin--horizontal)",
"fontFamily": "var(--wp--preset--font-family--monospace)"
}
},
"core/cover": {
// The keys for these selectors could match up with keys in
// block.json. An example of the block.json can be found in this
// gist. The important part is that this will allow us to target
// specific parts of blocks to apply different kinds of filters.
"selectors": {
"duotone": {
"filter": "var(--wp--preset--duotone--high-contrast)"
}
}
},
"core/image": {
"selectors": {
"duotone": {
// The custom duotone filter was defined in
// `customPresets.blocks['core/image'].root.customDuotone`.
// We can combine filters here because it is just CSS.
"filter": "var(--wp--custom--custom-duotone--white-and-black) drop-shadow( 5px 5px 8px rgba(0, 0, 0, 0.5) )"
}
}
},
"core/list": {
"root": {
"paddingLeft": "calc( 2 * var(--wp--custom--margin--horizontal) )"
}
},
"core/navigation": {
"root": {
"fontSize": "var(--wp--preset--font-size--normal)"
}
},
"core/post-author": {
"root": {
"fontWeight": "normal"
}
},
"core/post-comment": {
"root": {
"fontSize": "var(--wp--preset--font-size--normal)",
"lineHeight": "var(--wp--custom--body--line-height)"
}
},
"core/post-content": {
"root": {
"paddingLeft": "20px",
"paddingRight": "20px"
}
},
"core/post-date": {
"root": {
"color": "var(--wp--custom--color--foreground)",
"fontSize": "var(--wp--preset--font-size--small)"
},
"elements": {
// Again, the anchor tag. See `customProperties.elements`.
"a": {
// Again, the color property. See `customProperties.elements`.
"color": "var(--wp--custom--color--accent)"
}
}
},
"core/post-title": {
"root": {
"fontFamily": "var(--wp--custom--h1--font-family)",
"fontSize": "var(--wp--custom--h1--font-size)",
"lineHeight": "var(--wp--custom--h1--line-height)"
}
},
"core/pullquote": {
"root": {
"borderWidth": "1px 0",
"borderStyle": "solid",
"paddingTop": "var(--wp--custom--margin--horizontal)",
"paddingBottom": "var(--wp--custom--margin--horizontal)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"paddingRight": "var(--wp--custom--margin--horizontal)",
"fontStyle": "italic",
"fontSize": "var(--wp--preset--font-size--huge)"
}
},
"core/quote": {
"root": {
"borderWidth": "0 0 0 1px",
"borderStyle": "solid",
"borderColor": "var(--wp--custom--color--primary)",
"paddingLeft": "var(--wp--custom--margin--horizontal)",
"fontSize": "var(--wp--preset--font-size--normal)",
"fontStyle": "normal"
}
},
"core/separator": {
"root": {
"borderWidth": "0 0 1px 0",
"borderStyle": "solid",
"borderColor": "currentColor",
"color": "var(--wp--custom--color--foreground)"
}
},
"core/site-title": {
"root": {
"fontSize": "72px",
"fontWeight": 700
}
}
}
}
// `customTemplates` and `templateParts` can probably be the same. I haven't
// included them because they weren't needed for the duotone thought experiment.
// This concludes the tour of my dream theme.json. Thank you for your time.
}
/* TODO: Write out what the theme-v2.json would generate. */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment