How-to guidesAdd a style tool

Add a style tool

Paths are relative to apps/frontend/src/app/(dashboard)/editor/[projectId]/[pageId]/Components/StyleTools/.

Field-based tool

Use when each property maps to one input.

  1. Open toolDefinitions.tsx and find the group in TOOL_GROUPS (or add a { id, title, tools: [] } group).

  2. Add a fieldTool(id, title, fields) entry:

    fieldTool("text-emphasis", "Text emphasis", [
        { kind: "select", property: "text-emphasis-style", label: "Style", options: ["none", "filled", "open", "dot", "circle"], allowCustom: true },
        { kind: "color", property: "text-emphasis-color", label: "Color" },
        { kind: "segmented", property: "text-emphasis-position", label: "Position", options: seg(["over right", "under right"]) },
    ]),
  3. For unit fields, add the property to @/Utils/Style/cssPropertyUnits.ts or pass units explicitly.

  4. Long option lists go in toolOptions.ts.

  5. Run npx tsc --noEmit, npx eslint on the folder, and check the tool in the editor on the Style, Inline Style and a matched-rule target.

Custom tool

Use when properties are coupled (layers, per-side targeting, shorthands).

  1. Create tools/MyTool.tsx. Read with useStyleTool().controlledStyle and write with useStyleWriter():

    export default function MyTool() {
        const { controlledStyle } = useStyleTool();
        const setProperties = useStyleWriter();
        return (
            <PropertyRow label="Inset shadow" properties={["box-shadow"]}>
                <MeasureInput
                    value={controlledStyle["box-shadow"] ?? ""}
                    onChange={(v, live) => setProperties({ "box-shadow": v }, live)}
                />
            </PropertyRow>
        );
    }
  2. Register it: customTool("my-tool", "My tool", ["box-shadow"], () => <MyTool />). The property list feeds the badge and search.

  3. Pass live through from every input that reports it, so drags collapse into one undo step.

  4. Write several properties in one setProperties({...}) call when they belong to one user action.

Common variations

  • New input type. Add a component to ../Inputs/ (controlled, onChange(value, live?)), a kind to FieldSpec in PropertyField.tsx, and a case in its switch. Add the kind to WIDE if it should stack.
  • Shorthand vs longhand. Prefer writing longhands; shorthands overwrite sibling longhands already in the rule.

Troubleshooting

  • The tool doesn’t appear in search. The words are matched against title, group title and properties; custom tools must list their properties.
  • A drag creates many undo steps. An input is calling onChange without live: true, or the tool drops the live argument.