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.