Docs

This page isn't translated yet

next

Custom text states

Inline styles editors can apply, and how they render.

For richer formatting (e.g. semantic colors that map to design-token values), register custom text states:

const userSysthemaConfig: SysthemaPayloadPluginOptions = {
  customTextStates: [
    {
      editor: ['root', 'region'],
      state: {
        color: {
          highlight: {
            label: 'Highlight',
            css: {
              backgroundColor: 'var(--color-highlight)',
              color: 'var(--color-highlight-text)',
            },
          },
        },
      },
    },
  ],
}

A text state is an inline styleLink to this section

css is applied as an inline style attribute — in the editor and on the rendered page alike. That is what lets it hold anything, and it is also the one rule to keep in mind: the values are substituted on the element the state sits on.

Naming a token directly, as above, works in both places. Referencing a variable of your own does not, unless you declare it in the right scope:

/* your admin stylesheet */
:root {
  --brand-gradient: linear-gradient(135deg, var(--color-brand-a), var(--color-brand-b));
}
css: {
  backgroundImage: 'var(--brand-gradient)'
}

That renders correctly on the public site and resolves to nothing in the admin. A var() inside a custom property is substituted where the property is declared, not where it is used — and in the Payload Admin the Systhema tokens are declared on the editor root, not on :root. So --brand-gradient looks up colors that do not exist in its scope, becomes invalid, and every element using it gets no value at all. There is no error and no warning; a state that pairs background-image with -webkit-text-fill-color: transparent renders as invisible text.

Declare the variable on the editor's own scope instead, which Systhema publishes as .systhema-editor-tokens:

/* your admin stylesheet — Payload's `admin.css` */
.systhema-editor-tokens {
  --brand-gradient: linear-gradient(135deg, var(--color-brand-a), var(--color-brand-b));
}

Systhema stamps that class onto every editor built from a tier factory — rootLexicalEditor, regionLexicalEditor, fragmentLexicalEditor, slotLexicalEditor, which are exactly the tiers a text state can target — using Lexical's own root-element API rather than a class name. Target it instead of Payload's internal .ContentEditable__root: that class is an implementation detail of @payloadcms/richtext-lexical, and if it is renamed your admin CSS stops resolving with nothing to point at the cause. The class name is also exported as SYSTHEMA_EDITOR_TOKENS_CLASS from @systhemaui/payload, for CSS you generate rather than write. A field built with a bare lexicalEditor() carries no Systhema feature and so no stamp — it still gets the tokens through Payload's own class, just not the owned one.