openpencil/packages/docs/user-guide/variables.md

89 lines
6.7 KiB
Markdown
Raw Normal View History

---
title: Variables
description: Design variables, collections, modes, and fill bindings in OpenPencil.
---
# Variables
Variables store reusable design tokens — colors, spacing values, and other properties — that can be bound to nodes. Change a variable's value and every node using it updates.
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
## Opening the Variables Dialog
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
Open it from **View → Variables…**, by searching "Variables" in the command palette, or, with no nodes selected, from the Variables section of the Design tab. **Expand** in the dialog's corner gives it most of the window.
The dialog lists the active collection's variables on the left, edits the selected variable or the collection on the right, and shows the stylesheet they produce below. In a narrow window or on a phone it shows one mode at a time, and a variable, the collection settings, or the stylesheet opens over the list with a back button.
## Collections
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
Variables are organized into collections. In a wide dialog they are listed in a sidebar with how many variables each holds; narrower, they are tabs, or a menu on a phone.
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
- **Switch collection** — click it in the sidebar or its tab
- **Create collection** — click **+** next to Collections, or the folder button in the toolbar
- **Rename or delete** — with no variable selected, the right side edits the collection: change its name, or delete it from the **⋯** menu next to the name
- **Switch attribute** — the attribute that turns manually switched modes on, `data-theme` by default for a collection named Theme; type another name, such as `data-color-scheme`, to match an existing codebase
## Modes
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
Each collection can have multiple modes (e.g., Light and Dark). Modes appear as value columns in the list, and a variable has a value for each mode. Manage them in the collection settings:
- **Add mode** — click **+** next to Modes
- **Rename** — edit the mode's name
- **Duplicate, set as default, delete** — use the **⋯** menu next to the mode
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
The default mode is **Always on** and goes in `:root`. Every other mode has **Applies when**, which says when the mode takes over in the exported stylesheet; the CSS it writes is shown underneath:
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
| Applies when | CSS |
| --- | --- |
| **Switched manually** | the collection's switch attribute set to the mode, such as `[data-theme="dark"]` for a Theme collection's Dark mode |
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
| **System is in dark mode** / **System is in light mode** | `@media (prefers-color-scheme: dark)` / `light` |
| **High contrast is on** | `@media (prefers-contrast: more)` |
| **Reduced motion is on** | `@media (prefers-reduced-motion: reduce)` |
| **Screen is narrower than** / **Screen is wider than** a width | `@media (max-width: 640px)` / `min-width` |
| **Container is narrower than** / **Container is wider than** a width | `@container (max-width: 640px)` / `min-width` |
| **Custom CSS** | any selector, or a `@media`, `@supports`, or `@container` query |
On the canvas, a layer shows a mode when you set the layer to it, whatever the condition. In exported code, a manually switched mode is turned on by adding its attribute to an element, so layers set to it export with that attribute. Layers set to a mode with any other condition, custom selectors included, export with literal values instead of tokens, because the stylesheet, not the layer, decides when that mode applies.
## Managing Variables
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
Variables are grouped by the folders in their names (`Brand/Primary` appears as *Primary* under *Brand*), with their CSS name and one value per mode.
- **Create variable** — click **Create variable** (or **+**) and pick a type; the new variable opens for editing
- **Select** — click a row, or move with the arrow keys and press Enter. Shift-click selects a range, and Cmd-click (Ctrl-click on Windows and Linux) adds or removes one
- **Filter** — type in the search bar to filter by name, CSS name, description, or value, such as `--color-brand`, a hex color, or the variable an alias points at, forgiving typos as the command palette does; click a group in the sidebar to show only that group and the groups inside it, or use the filter button to show only some types
- **Rename or edit in place** — double-click a name, or a number or text value, in the list
- **Right-click** — rename, duplicate, move to a group (or a new one), or delete the selected variables; Delete or Backspace deletes them too
- **Reorder** — drag a row; the order is kept in the file
- **Several selected** — the right side moves them into a group, duplicates them, or deletes them
- **Undo and redo** — Cmd+Z and Cmd+Shift+Z or Cmd+Y (Ctrl on Windows and Linux) work in the dialog as on the canvas, one step per change. Enter commits a field and returns to the list; while a field has text you have not committed, Cmd+Z undoes the typing
Selecting a variable edits:
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
- **Name** and **CSS name** — leave the CSS name empty to derive it from the name and scopes, such as `--color-brand-primary`. Type the name without its `--`; a name CSS cannot use is flagged and not saved
- **Unit** — for numbers, `px`, `rem`, `%`, `ms`, `s`, `deg`, or none; values are entered in that unit
- **Value** (or **Values**, one per mode) — a color opens the color picker. The variable button next to a value points it at another variable of the same type, an alias that follows that variable; **Detach variable** turns it back into the value it showed
- **CSS expression** — for numbers, a value such as `clamp(1rem, 4vw, 1.5rem)` written instead of the number in CSS, while the canvas keeps drawing the number
- **Scopes** — which properties the variable is offered for
- **Description**
- **Hide from publishing** — files that use this one as a library do not see the variable
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
## Stylesheet
feat: edit variables as tokens in the variables dialog (#907) * feat: prototype the design tokens panel Undoable editor actions for token fields and mode conditions, a token view model, and table, inspector, collection and stylesheet panels shown in a Storybook story with a real editor. * feat: lay the tokens panel out for mobile Below the mobile breakpoint the collection tabs become a select, the list shows one chosen mode with the CSS name under each token, and the token, the modes, and the stylesheet each open over the list behind Back. CodeViewer can fill its container for the full-screen stylesheet. * feat: share drill-in navigation and put tokens on a listbox PanelDrillIn gives detail views one back control that names where it returns, a slide preset from theme/motion, and focus that moves into the detail and back to what opened it. The Settings model editor and the tokens panel's mobile views use it. The token list is a Reka Listbox with arrow-key navigation and labelled groups, and the inspector and stylesheet swap with a short fade under the motion policy. * feat: lay the tokens panel out by container width The panel switches to the compact drill-in by its own measured width, and the list's columns follow the list's width through container queries, so the panel adapts inside dialogs and split views, not only on phones. src/AGENTS.md now says when to use container queries, measured size, and viewport breakpoints. * test(core): group the variable undo tests in a domain folder * feat: edit variables as tokens in the variables dialog The variables dialog now hosts the tokens panel: a grouped token list with CSS names, an inspector for each token and for the collection and its modes, and the live stylesheet. It keeps adding variables by type, search, collection and mode management, and copying the document's stylesheet, and lays out by its own width down to phones. useVariables().collections returns copies, so components given a collection see modes added or renamed in place, and addVariable returns the new ID so the panel can open it. * fix: satisfy the type-aware lint in token updates and panel stories * docs: describe the tokens panel in the translated variables guides * feat: say when each mode applies in plain words A mode's condition was a raw CSS field that designers could not read. Each non-default mode now picks when it applies (switched manually, system dark or light mode, high contrast, reduced motion, screen or container width, or custom CSS), with the CSS it writes shown underneath and a note on how the mode behaves on the canvas and in exported code. Presets only write the condition string modes already store, so files round-trip unchanged. Column headers name the condition in words, and deleting the collection moves into a menu beside its name. * feat: bring the variables dialog up to Figma's The dialog now covers what Figma's variables dialog offers: collections and groups in a sidebar with counts, a group, search and type filter, type icons, names and values edited in place, drag to reorder, multi-select with a context menu, the Delete key and a side panel to duplicate, group and delete, aliases chosen from a variable picker and detached back to the value they showed, hiding from publishing, and an expand toggle. It opens from View → Variables… and the command palette as well as the Design panel. It also fixes review findings: a single-mode collection labels its value Value, column titles share one font, a CSS name is typed after a fixed -- and checked by the CSS parser before it is saved, and the stylesheet previews CSS with the format chosen when copying. AppInput applies an instance's input classes last so they can override adornment padding. * feat: undo and redo inside the variables dialog Canvas shortcuts stop while any dialog is open, so Cmd+Z did nothing in the variables dialog although every edit there is on the editor's history. Undo and redo now take a document scope: they also run when the topmost layer is a dialog that edits the document, which the variables dialog marks, while menus, pickers and Settings still hold them back and a field with uncommitted text keeps its own undo. Enter commits a field and returns focus to the list. The panel drops selections, group filters and collections that undo removed. A color picker session undoes as one step through a coalesce key on updateVariableValue, and undoing a deletion puts the variable back in its place. * feat: search variables like the command palette The variables search matched a substring of the name only, so a CSS name, a hex color or a description found nothing, and the palette, AppPicker and AppCombobox each spelled out the same Fuse.js options. One helper in @open-pencil/vue now owns the matching: fuzzySearch ranks results for lists people pick from, and fuzzyFilter keeps a list's own order for lists people arrange. The panel searches names, CSS names, descriptions and every mode's value, alias names included; useVariables() searches names and descriptions. * fix: say a token group once when its name repeats it Kits that mirror Tailwind classes name tokens like Gap/gap-1, which derived --gap-gap-1. A group the next segment repeats is now said once, giving --gap-1. * fix: keep the add variable menu under its button after a resize The toolbar swaps the icon button for the labelled one when the dialog widens. Reka keeps the anchor it mounted with, so the menu opened at the window corner; keying the trigger remounts it with the new button. * fix: leave bound layers alone when variables are added or reordered Adding, copying, or reordering variables re-resolved every bound layer in the document. In the shadcn kit two Avatar instances are saved at 24 while their binding gives 40, so adding a number resized them and relaid out about 7,800 layers, freezing the browser for seconds. These changes alter no bound value and now only request a render, as renaming already did. * refactor: let the variables dialog own its undo shortcuts Undo and redo in the dialog went through a document scope in the global shortcut registry, which decided whether the dialog was on top by querying Reka's dismissable layers in DOM order. The dialog now listens on its own content with a tinykeys handler built from the command keybindings: menus and pickers it opens portal elsewhere, so their keys never reach it, and the registry is back to one scope. * refactor: read mode conditions with css-tree Presets were recognized by normalizing the condition with regular expressions and matching another. css-tree, which Core already ships through unifont, now parses the condition into its media or container feature, so spacing, case and comments are handled as CSS does, and a non-breaking space, which CSS does not treat as whitespace, no longer turns a custom condition into a preset. * refactor: take the mode attribute hint from modeAttribute The hint for a manually switched mode cut the brackets off its selector with a regular expression. It now reads the attribute name and value from modeAttribute, which the selector is built from. * fix: keep a refused CSS name in focus and color picker sessions apart Enter on a CSS name the parser refuses no longer hands the keyboard back to the list, so the name can be corrected. The typed name is read with parseCSSName instead of stripping a leading -- by hand, so a pasted var(--name) works too. Each color picker session takes a random undo key; a counter restarted when the inspector remounted, so two sessions on the same token could merge into one undo step. * fix: keep token expressions and cleared fields in step with their variable Editing a number left its CSS expression recording the old number, so reopening the file dropped the expression as edited elsewhere. A number now updates its expression, and an alias drops it, in the same undo step. Undoing a token field that had been unset kept the key with an undefined value; it is now removed. * fix: preview and detach aliases in their own mode An alias in the Dark column showed, and detached to, what its target gives in the mode the canvas is in. Both now resolve in the column's mode. * fix: drop tokens a filter hides from the selection A search, group, or type filter could hide selected tokens that stayed selected, so the inspector, the bulk actions, and the Delete key still acted on rows the list no longer showed. * fix: keep a drill-in's list out of the tab order while its detail slides The list stayed focusable until the detail finished sliding in, and became focusable again under a detail sliding out. It is now inert from the moment the detail opens and the departing detail is inert. A detail with no field focuses its back control, and hidden inputs are skipped. * docs: drop a duplicated Stroke entry from the changelog Two merges left the Stroke-extends-Fill breaking change twice; the copy that still said strokes render solid only is outdated, since gradient and image strokes now import and render.
2026-10-06 12:35:40 +00:00
The bottom of the dialog shows the active collection as CSS custom properties. The copy button copies the whole document's variables as CSS or as a Tailwind v4 theme, so aliases to other collections resolve.
## Binding Variables to Fills
In the Fill section of the properties panel, use the variable picker to bind a color variable to a node's fill.
- **Bind** — select a color variable from the picker. The fill shows a purple badge with the variable name.
- **Detach** — click the detach button on the badge to remove the binding. The fill reverts to the resolved color value.
When the variable's value changes (or when switching modes), all bound fills update automatically.
## Tips
- Use collections to group related tokens (e.g., "Primitives" for raw colors, "Semantic" for role-based aliases, "Spacing" for layout values).
- Modes are useful for theme switching — define Light and Dark mode values in the same collection.
- Variables support aliases — a "Semantic" collection can reference values from a "Primitives" collection.
- See [Drawing Shapes](./drawing-shapes) for how fills and the color picker work.