{"id":"l-fig-3-3","courseId":"figma-ui-design","lang":"en","kind":"lesson","summary":"Decide when a value belongs in a style or a variable, set up Steady’s primitive and semantic color variables with aliases, and bind components to meanings instead of raw hex codes.","html":"<p>Search Steady’s file for the hex code #0F766E and you will find it on check buttons, the primary button, a focus ring, a link and a progress ring, typed in by hand each time. When the brand team asks for a slightly bluer teal, you face the same hunt as in Lesson 3.1, this time for a color. Worse, when dark mode arrives, some of those places need a lighter teal and some do not, and the hex code cannot tell you which.</p>\n<p>Styles and variables both put values in one place. They solve slightly different problems.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Styles: reusable composites</h2>\n<p>A <strong>style</strong> is a named bundle of visual properties. At the time of writing, Figma has color styles, text styles, effect styles and layout guide styles. You already planned text styles in Lesson 1.3: <code dir=\"ltr\">Headline</code> is a font family, a size, a weight, a line height and letter spacing, all applied in one click.</p>\n<p>Styles shine when the value is a <strong>composite</strong>. A text style is several properties at once. An effect style can hold a stack of two shadows. A color style can hold a gradient or several layered fills. Change the style and every layer using it updates.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Variables: single values with superpowers</h2>\n<p>A <strong>variable</strong> holds one raw value. The core types are <strong>color</strong>, <strong>number</strong>, <strong>string</strong> and <strong>boolean</strong> (Figma has added further types for motion work since). You can bind variables to fills and strokes, corner radius, gap, padding, width and height, opacity, layer visibility, text content and many typography properties.</p>\n<p>Three things make variables different from styles:</p>\n<ul>\n<li><strong>Aliasing</strong>: a variable can point at another variable of the same type, instead of holding a raw value.</li>\n<li><strong>Modes</strong>: a variable can hold a different value per mode, such as light and dark (next lesson).</li>\n<li><strong>Tokens</strong>: variables map closely to the design tokens developers use in code, and at the time of writing you can record each variable’s code name for web, iOS and Android so Dev Mode shows it.</li>\n</ul>\n<p>Variables live in <strong>collections</strong>, and slashes in names create groups: <code dir=\"ltr\">color/text/primary</code> sits in the group <code dir=\"ltr\">color/text</code>.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Two tiers: primitives and semantics</h2>\n<p>The pattern most design systems use, and the one Steady uses, has two tiers.</p>\n<p><strong>Primitives</strong> are the raw palette, named by what they are: <code dir=\"ltr\">teal/700</code> = #0F766E, <code dir=\"ltr\">gray/500</code> = #6B7280, <code dir=\"ltr\">white</code> = #FFFFFF. They have no opinion about where they are used.</p>\n<p><strong>Semantic</strong> variables are named by what they are for, and alias a primitive: <code dir=\"ltr\">color/action/primary</code> → <code dir=\"ltr\">teal/700</code>, <code dir=\"ltr\">color/text/secondary</code> → <code dir=\"ltr\">gray/500</code>, <code dir=\"ltr\">color/surface/default</code> → <code dir=\"ltr\">white</code>. Components bind <strong>only</strong> to semantic variables.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 250\" role=\"img\" aria-labelledby=\"l-fig-3-3-t1\"><defs><marker id=\"arrow-l-fig-3-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-fig-3-3-t1\">Three columns. Primitives teal 700, gray 500 and white feed semantic variables action primary, text secondary and surface default, which are bound to the check button fill, the streak text and the row background.</title>\n  <text class=\"d-label-strong\" x=\"110\" y=\"26\" text-anchor=\"middle\">Primitives</text>\n  <text class=\"d-label-strong\" x=\"340\" y=\"26\" text-anchor=\"middle\">Semantic</text>\n  <text class=\"d-label-strong\" x=\"570\" y=\"26\" text-anchor=\"middle\">Component use</text>\n  <rect class=\"d-box\" x=\"30\" y=\"44\" width=\"160\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"110\" y=\"69\" text-anchor=\"middle\">teal/700</text>\n  <rect class=\"d-box\" x=\"30\" y=\"114\" width=\"160\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"110\" y=\"139\" text-anchor=\"middle\">gray/500</text>\n  <rect class=\"d-box\" x=\"30\" y=\"184\" width=\"160\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"110\" y=\"209\" text-anchor=\"middle\">white</text>\n  <rect class=\"d-box-primary\" x=\"240\" y=\"44\" width=\"200\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"340\" y=\"69\" text-anchor=\"middle\">action/primary</text>\n  <rect class=\"d-box-primary\" x=\"240\" y=\"114\" width=\"200\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"340\" y=\"139\" text-anchor=\"middle\">text/secondary</text>\n  <rect class=\"d-box-primary\" x=\"240\" y=\"184\" width=\"200\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"340\" y=\"209\" text-anchor=\"middle\">surface/default</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"44\" width=\"160\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"570\" y=\"69\" text-anchor=\"middle\">Check fill</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"114\" width=\"160\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"570\" y=\"139\" text-anchor=\"middle\">Streak text</text>\n  <rect class=\"d-box-accent\" x=\"490\" y=\"184\" width=\"160\" height=\"40\" rx=\"8\"/>\n  <text class=\"d-label\" x=\"570\" y=\"209\" text-anchor=\"middle\">Row background</text>\n  <path class=\"d-arrow\" d=\"M238 64 L192 64\" marker-end=\"url(#arrow-l-fig-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M238 134 L192 134\" marker-end=\"url(#arrow-l-fig-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M238 204 L192 204\" marker-end=\"url(#arrow-l-fig-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M488 64 L442 64\" marker-end=\"url(#arrow-l-fig-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M488 134 L442 134\" marker-end=\"url(#arrow-l-fig-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M488 204 L442 204\" marker-end=\"url(#arrow-l-fig-3-3-1)\"/>\n</svg><figcaption>Components point at meanings, meanings point at the palette</figcaption></figure>\n<p>Why bother with the middle column? Because meaning and value change for different reasons. The brand team changes <code dir=\"ltr\">teal/700</code>, and every action color follows. Dark mode changes what <code dir=\"ltr\">color/action/primary</code> points at, and only action colors follow, not every teal in the file. Neither change requires touching a single component.</p>\n<p>Steady’s numbers get the same treatment: a <code dir=\"ltr\">space</code> group (<code dir=\"ltr\">space/1</code> = 4 through <code dir=\"ltr\">space/12</code> = 48, from Lesson 2.3) and a <code dir=\"ltr\">radius</code> group (<code dir=\"ltr\">radius/sm</code> = 8, <code dir=\"ltr\">radius/md</code> = 12). Bind the habit row’s padding to <code dir=\"ltr\">space/3</code> and <code dir=\"ltr\">space/4</code>, and its corner radius to <code dir=\"ltr\">radius/md</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Semantic names that describe appearance</span></p>\n<p><code dir=\"ltr\">color/teal-button</code> is a primitive wearing a semantic costume. The day the button turns blue, the name lies, and in dark mode it may not even be teal. Name semantic variables by role, such as <code dir=\"ltr\">action/primary</code>, <code dir=\"ltr\">text/secondary</code> or <code dir=\"ltr\">border/default</code>, and let the value be whatever that role needs.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Keeping the picker clean</h2>\n<p>Two settings keep people on the system. <strong>Scoping</strong> limits where a variable is offered: scope <code dir=\"ltr\">space</code> variables to gap and padding, and they stop appearing in the corner radius picker. <strong>Hiding from publishing</strong> keeps primitives out of the library that other files see, so designers using the kit only find semantic tokens. Both live in each variable’s edit panel at the time of writing.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Use both</h2>\n<p>Variables did not replace styles. The usual split is: variables for single values (colors, spacing, radius, booleans) because they alias and switch modes; text styles for typography, with their sizes bound to number variables if you need them to change per mode; effect styles for shadows; color styles only for gradients or layered fills. Bind a color style’s fill to a variable and you get the composite’s convenience with the variable’s modes.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Start semantic tokens small</span></p>\n<p>Steady needs about a dozen semantic colors: text primary and secondary, surface default and raised, border default, action primary and its pressed state, on-action (text on teal), success, danger and focus. Add a token when a real component needs a meaning that does not exist yet, not before.</p>\n</aside>\n<p>With every component pointing at meanings, dark mode becomes a question of what each meaning points at, which is exactly what modes answer next.</p>\n","toc":[{"id":"sec-1","text":"Styles: reusable composites","level":2},{"id":"sec-2","text":"Variables: single values with superpowers","level":2},{"id":"sec-3","text":"Two tiers: primitives and semantics","level":2},{"id":"sec-4","text":"Keeping the picker clean","level":2},{"id":"sec-5","text":"Use both","level":2}],"takeaways":["A style bundles several properties into one reusable composite, such as a text style’s font, size, weight and line height.","A variable holds one raw value (color, number, string or boolean), can alias another variable, and can change per mode.","Use two tiers of variables, primitives for the raw palette and semantic tokens for meanings, and bind components only to the semantic tier.","Text styles, effect styles and multi-fill or gradient color styles still have no single-variable equivalent, so most teams use both systems.","Scoping and hiding primitives from publishing keep the variable picker short and stop people from reaching for raw values."],"further":[{"title":"Overview of variables, collections, and modes","url":"https://help.figma.com/hc/en-us/articles/14506821864087-Overview-of-variables-collections-and-modes"},{"title":"Guide to styles in Figma","url":"https://help.figma.com/hc/en-us/articles/360039238753-Guide-to-styles-in-Figma"},{"title":"Create and manage variables and collections","url":"https://help.figma.com/hc/en-us/articles/15145852043927-Create-and-manage-variables-and-collections"}],"quiz":[{"q":"<p>The Done state of the habit row uses the check color <code dir=\"ltr\">teal/700</code> directly from the primitives collection. Next sprint you add dark mode. What goes wrong?</p>","options":[{"text":"Nothing, because primitives automatically switch in dark mode.","why":"Primitives are fixed raw values. Switching is the job of semantic variables that have a value per mode."},{"text":"Figma refuses to publish components bound to primitives.","why":"Figma allows it. The problem is that you lose the layer of meaning that modes switch on."},{"text":"The row keeps the dark teal in dark mode, because nothing says this color means “action” and should change.","why":"Correct. Binding to <code dir=\"ltr\">color/action/primary</code> instead lets dark mode point that meaning at a lighter teal."},{"text":"The habit row loses its variants.","why":"Variable bindings do not affect variants or other component properties."}],"answer":2},{"q":"<p>Which of these belongs in a style rather than a single variable?</p>","options":[{"text":"The Headline text treatment, 17 px semibold with a 24 px line height.","why":"Correct. It is a composite of several properties. A text style bundles them, and each part can still be bound to a variable."},{"text":"The 16 px card padding.","why":"Padding is a single number, which is exactly what a number variable is for."},{"text":"The primary action color.","why":"A single color that should change per mode is a classic color variable."},{"text":"Whether the onboarding hint is visible.","why":"A true or false value fits a boolean variable."}],"answer":0},{"q":"<p>What is the main benefit of aliasing <code dir=\"ltr\">color/text/secondary</code> to <code dir=\"ltr\">gray/500</code> instead of typing #6B7280 into it?</p>","options":[{"text":"Aliases make the file smaller.","why":"File size barely changes. The value is about maintenance and meaning."},{"text":"Developers cannot read raw hex values in Dev Mode.","why":"Dev Mode shows hex values fine. The benefit is the chain of meaning."},{"text":"Aliased variables cannot be edited by accident.","why":"Aliases can still be repointed. They are not a lock."},{"text":"Changing the palette’s gray once updates every meaning that points at it, and each mode can point somewhere different.","why":"Correct. Aliases separate what a color is from what it is for, so palette changes and modes stay one edit each."}],"answer":3},{"q":"<p>A designer types 15 into a gap field even though a <code dir=\"ltr\">space</code> variable picker is available. What setup change best discourages this?</p>","options":[{"text":"Delete the gap field from the file settings.","why":"You cannot remove core properties. The goal is to make the right value the easy one."},{"text":"Scope the spacing variables to gap and padding so they appear first there, and review off-system values with a design check before handoff.","why":"Correct. Scoping keeps the picker relevant, and a review catches hard-coded values that slipped in."},{"text":"Rename every spacing variable to its pixel value.","why":"Names like <code dir=\"ltr\">16</code> hide meaning and do not stop anyone typing 15."},{"text":"Convert all spacing into text styles.","why":"Text styles hold typography, not layout spacing."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"json","code":"{\n  \"primitives\": {\n    \"teal/700\": \"#0F766E\",\n    \"gray/900\": \"#1A1A1A\",\n    \"gray/500\": \"#6B7280\",\n    \"gray/400\": \"#9CA3AF\",\n    \"gray/200\": \"#E5E7EB\",\n    \"white\":    \"#FFFFFF\"\n  },\n  \"semantic\": {\n    \"color/text/primary\":    \"{gray/900}\",\n    \"color/text/secondary\":  \"{gray/400}\",\n    \"color/surface/default\": \"{white}\",\n    \"color/border/default\":  \"{gray/200}\",\n    \"color/action/primary\":  \"#0F766E\",\n    \"color/teal-checkbox\":   \"{teal/700}\",\n    \"color/on-action\":       \"{white}\"\n  }\n}","bugLines":[12,15,16],"title":"Review Steady's color variables","prompt":"<p>Here is an export of Steady’s light-mode color variables. <code dir=\"ltr\">{name}</code> means the variable aliases another variable. The kit’s rules: semantic variables alias primitives (never raw values), are named by role (never by appearance), and every text color passes 4.5:1 on white. Select every line that breaks a rule.</p>\n","hints":["Check each semantic line three ways: is it an alias, is the name a role, and does a text color pass contrast?","From Lesson 1.3: #6B7280 on white is 4.83:1, but #9CA3AF on white is only 2.54:1.","One line fails contrast, one hard-codes a value, and one names a color by its appearance."],"explanation":"<p>Line 12: <code dir=\"ltr\">color/text/secondary</code> aliases <code dir=\"ltr\">gray/400</code> (#9CA3AF), which is <strong>2.54:1</strong> on white and fails for text. Point it at <code dir=\"ltr\">gray/500</code> (4.83:1). Line 15: <code dir=\"ltr\">color/action/primary</code> holds a <strong>raw hex</strong> instead of aliasing <code dir=\"ltr\">{teal/700}</code>, so a palette change will not reach it and it breaks the two-tier pattern. Line 16: <code dir=\"ltr\">color/teal-checkbox</code> is <strong>named by appearance and by one component</strong>. Rename it to a role such as <code dir=\"ltr\">color/action/primary</code> (it is the same meaning, so you can delete it and use that) or <code dir=\"ltr\">color/control/checked</code> if checked controls need their own token.</p>\n","checkLabels":{}},"words":786,"readMinutes":4}