{
    "componentChunkName": "component---src-templates-handbook-tsx",
    "path": "/handbook/nuts/components",
    "result": {"data":{"markdownRemark":{"html":"<p>The UI is built by composing components. Pages do not have their own stylesheets — all styling lives in the components they compose. In a React app a page component owns both data fetching and UI composition, so the two responsibilities live together in one file. In a Rails full-stack app the split is clearer: data handling belongs in the controller and the view component is purely about composing presentational components.</p>\n<p>Working on components means close collaboration with designers and Figma. Before implementing anything, the first place to check is the shaping section of the GitHub issue — it will tell you what components are expected and whether they are new or already exist. Components are named in code the same way they are named in Figma, so the design file is a reliable indicator of whether a component has already been implemented. If the project has Storybook set up, that is another quick way to browse what is already available before writing anything new.</p>\n<p>Once a feature is implemented, test it across different browsers and operating systems before marking it done. Visual bugs that only appear on specific platforms are common and easy to catch early with a quick cross-browser check.</p>\n<h2 id=\"presentational-dumb-components\" style=\"position:relative;\"><a href=\"#presentational-dumb-components\" aria-label=\"presentational dumb components permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Presentational (Dumb) Components</h2>\n<p>Presentational components own their markup and styling. They receive all their data through props, make no API calls, and hold no application state — they are only responsible for how things look.</p>\n<p>When building a component or page, reach for these primitives first and compose them together. If a layout or styling requirement cannot be met with the available primitives, check whether a new primitive is warranted before adding ad hoc markup. The rule of three applies: only extract a new primitive once the same pattern has appeared independently in at least three places. A one-off layout belongs in the component that needs it, not in shimmer-components.</p>\n<h3 id=\"file-structure\" style=\"position:relative;\"><a href=\"#file-structure\" aria-label=\"file structure permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>File Structure</h3>\n<p>Each component lives in its own folder under <code>src/components</code>, containing exactly two files: the component itself and its stylesheet.</p>\n<pre><code>src/components/document_card/document_card.tsx\nsrc/components/document_card/document_card.scss\n</code></pre>\n<p>The folder name, file names, and BEM block in the stylesheet must all match. The component identifier in code uses PascalCase (<code>DocumentCard</code>) while the file and folder use <code>snake_case</code>.</p>\n<p>When a component needs to be targeted by JavaScript (e.g. for event delegation or test selectors), use <code>data-</code> attributes rather than IDs or class names. Name them after the role, not the implementation: <code>data-action=\"submit\"</code> rather than <code>data-button=\"true\"</code>. Never use BEM class names as JS hooks — classes are for styling only.</p>\n<h3 id=\"primitives\" style=\"position:relative;\"><a href=\"#primitives\" aria-label=\"primitives permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Primitives</h3>\n<p>Every project ships the same set of low-level layout and typography components. These primitives are provided by the <a href=\"https://github.com/nerdgeschoss/shimmer-components\">shimmer-components</a> library and should be the first thing you reach for when composing any component or page. Do not re-implement layout patterns from scratch — use these instead.</p>\n<p>If a primitive you need does not exist in <a href=\"https://github.com/nerdgeschoss/shimmer-components\">shimmer-components</a>, implement it according to the correct Figma design in the project before using it. Do not add ad hoc markup as a substitute for a missing primitive.</p>\n<blockquote>\n<p><strong>Note:</strong> <code>Grid</code> and <code>Icon</code> are not yet part of shimmer-components. Copy the implementation from an existing project — ask a senior developer for the recommended source.</p>\n</blockquote>\n<h4 id=\"grid\" style=\"position:relative;\"><a href=\"#grid\" aria-label=\"grid permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Grid</h4>\n<p>Use <code>Grid</code> whenever you need to arrange content in a two-dimensional layout. Define column, row, and gap settings per breakpoint using the <code>mobile</code>, <code>tablet</code>, <code>desktop</code>, and <code>desktopLg</code> props.</p>\n<pre><code class=\"language-tsx\">&#x3C;Grid\n  mobile={{ gap: 8 }}\n  desktop={{ columns: 'repeat(2, 1fr)' }}\n  desktopLg={{ gap: 16 }}\n>\n  &#x3C;Card />\n  &#x3C;Card />\n&#x3C;/Grid>\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex — grid applies layout via CSS custom properties\ngrid(\n  mobile: { gap: 8 },\n  desktop: { columns: \"repeat(2, 1fr)\" },\n  desktop_lg: { gap: 16 }\n) do\n  # child components\nend\n</code></pre>\n<p>Prefer <code>Grid</code> over recreating grid layout with custom CSS in a component's stylesheet. Custom grid layouts are sometimes necessary, but reach for <code>Grid</code> first.</p>\n<h4 id=\"icon\" style=\"position:relative;\"><a href=\"#icon\" aria-label=\"icon permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Icon</h4>\n<p>Use <code>Icon</code> to render an SVG icon by name. Always pass a <code>size</code> prop in pixels. <code>Icon</code> is purely presentational — it is never interactive on its own. Buttons that display an icon use a <code>Button</code> or <code>IconButton</code> component that wraps <code>Icon</code>.</p>\n<pre><code class=\"language-tsx\">&#x3C;Icon name=\"arrow-new-tab\" size={16} />\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex\nicon(name: \"arrow-new-tab\", size: 16)\n</code></pre>\n<h4 id=\"stack\" style=\"position:relative;\"><a href=\"#stack\" aria-label=\"stack permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Stack</h4>\n<p>Use <a href=\"https://github.com/nerdgeschoss/shimmer-components/tree/main/components/stack\"><code>Stack</code></a> whenever you need to lay out a series of elements in a single direction. The default direction is vertical. Add the <code>line</code> prop for a horizontal row. Pass <code>gap</code>, <code>align</code>, and <code>justify</code> to control spacing and alignment.</p>\n<pre><code class=\"language-tsx\">{\n  /* Horizontal row with space between and centered alignment */\n}\n&#x3C;Stack gap={0} line justify=\"space-between\" align=\"center\">\n  &#x3C;Text type=\"caption-bold\">{i18n.t('document.title')}&#x3C;/Text>\n  &#x3C;Icon name=\"arrow-new-tab\" size={16} />\n&#x3C;/Stack>;\n\n{\n  /* Vertical stack */\n}\n&#x3C;Stack gap={16}>\n  &#x3C;Text type=\"h2\">{i18n.t('section.heading')}&#x3C;/Text>\n  &#x3C;Text type=\"body\">{i18n.t('section.description')}&#x3C;/Text>\n&#x3C;/Stack>;\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex — horizontal row\nstack(line: true, justify: \"space-between\", align: \"center\") do\n  text(type: \"caption-bold\") { t(\"document.title\") }\n  icon(name: \"arrow-new-tab\", size: 16)\nend\n\n# Phlex — vertical stack\nstack(gap: 16) do\n  text(type: \"h2\") { t(\"section.heading\") }\n  text(type: \"body\") { t(\"section.description\") }\nend\n</code></pre>\n<p><code>Stack</code> is best suited for page-level layout and smart components where you want to control spacing without adding a new stylesheet. For complex dumb components, defining flex layout directly in the component's SCSS is often the better choice — it avoids an extra wrapper element, keeps the markup flatter, and makes viewport-specific adjustments easier to reason about in one place.</p>\n<h4 id=\"box\" style=\"position:relative;\"><a href=\"#box\" aria-label=\"box permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Box</h4>\n<p>Use <a href=\"https://github.com/nerdgeschoss/shimmer-components/tree/main/components/box\"><code>Box</code></a> when you need to apply padding around content or constrain the width of a section. Pass a <code>padding</code> object with <code>vertical</code>, <code>horizontal</code>, <code>top</code>, <code>bottom</code>, <code>left</code>, or <code>right</code> values in pixels.</p>\n<pre><code class=\"language-tsx\">&#x3C;Box padding={{ vertical: 16, horizontal: 24 }}>\n  &#x3C;Stack gap={8} line align=\"center\">\n    &#x3C;Icon name=\"document\" size={24} />\n    &#x3C;Text type=\"caption-bold\">{i18n.t('document.name')}&#x3C;/Text>\n  &#x3C;/Stack>\n&#x3C;/Box>\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex\nbox(padding: { vertical: 16, horizontal: 24 }) do\n  stack(gap: 8, line: true, align: \"center\") do\n    icon(name: \"document\", size: 24)\n    text(type: \"caption-bold\") { t(\"document.name\") }\n  end\nend\n</code></pre>\n<p>Avoid using <code>Box</code> solely for visual decoration. If you need a background color or border, that belongs in the component's own BEM styles, not in <code>Box</code> props.</p>\n<h4 id=\"collapse\" style=\"position:relative;\"><a href=\"#collapse\" aria-label=\"collapse permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Collapse</h4>\n<p>Use <a href=\"https://github.com/nerdgeschoss/shimmer-components/tree/main/components/collapse\"><code>Collapse</code></a> to show or hide content in response to user interaction. Use it for accordions, expandable sections, and any other toggle-reveal pattern.</p>\n<pre><code class=\"language-tsx\">const [open, setOpen] = useState(false);\n\n&#x3C;Collapse open={open}>\n  &#x3C;Button onClick={() => setOpen(!open)} />\n  &#x3C;Text type=\"body\">{i18n.t('section.details')}&#x3C;/Text>\n&#x3C;/Collapse>;\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex\ncollapse(open: false) do\n  text(type: \"body\") { t(\"section.details\") }\nend\n</code></pre>\n<h4 id=\"text\" style=\"position:relative;\"><a href=\"#text\" aria-label=\"text permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Text</h4>\n<p>Use <code>Text</code> for all rendered copy that requires a specific typographic style. Pass the desired variant via the <code>type</code> prop — the available types are generated directly from the Figma design variables (see the <a href=\"#figma\">Figma</a> section), so they always match what is in the design file. Never use bare HTML heading or paragraph tags when a specific style is intended.</p>\n<p>The <code>type</code> and <code>color</code> values are defined in the project's <code>types.ts</code>, which is generated from Figma variables. Always use these TypeScript types rather than raw strings to keep values in sync with the design system.</p>\n<p><code>Text</code> accepts an <code>element</code> prop to control which HTML node is rendered, keeping semantic markup correct independently of the visual style. When no <code>element</code> is given, <code>Text</code> renders as a <code>div</code> — it never infers the element from the <code>type</code>, so visual style and semantic element are always chosen explicitly. The <code>type</code> already handles responsive sizing across all viewports. Additional helper props like <code>uppercase</code>, <code>inline</code>, and <code>noWrap</code> cover common typographic adjustments, and text color is also managed through <code>Text</code> rather than custom CSS.</p>\n<pre><code class=\"language-tsx\">{/* Correct */}\n&#x3C;Text type=\"h1\">{i18n.t('page.title')}&#x3C;/Text>\n&#x3C;Text type=\"caption-bold\">{i18n.t('document.label')}&#x3C;/Text>\n\n{/* Wrong — do not use bare HTML tags for styled text */}\n&#x3C;h1>Page title&#x3C;/h1>\n&#x3C;strong>Document label&#x3C;/strong>\n\n{/* Control semantic element independently of visual style */}\n&#x3C;Text type=\"h1\" element=\"h2\">{i18n.t('section.heading')}&#x3C;/Text>\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex — correct\ntext(type: \"h1\") { t(\"page.title\") }\ntext(type: \"caption-bold\") { t(\"document.label\") }\n\n# Phlex — wrong: do not use bare HTML tags for styled text\nh1 { \"Page title\" }\nstrong { \"Document label\" }\n\n# Control semantic element independently of visual style\ntext(type: \"h1\", element: :h2) { t(\"section.heading\") }\n</code></pre>\n<h3 id=\"states\" style=\"position:relative;\"><a href=\"#states\" aria-label=\"states permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>States</h3>\n<p>Every component that can wait, fail, or return nothing must account for all three conditions explicitly. Do not leave loading, error, or empty states as afterthoughts — design and implement them alongside the happy path.</p>\n<ul>\n<li><strong>Loading</strong> — a skeleton that matches the shape of the loaded content is usually a good starting point. Spinners tend to work better for full-page transitions than for inline content.</li>\n<li><strong>Error</strong> — a message that tells the user what went wrong and, where possible, what they can do about it is generally preferable to a silent failure.</li>\n<li><strong>Empty</strong> — a short label or a call to action is almost always better than a blank region.</li>\n</ul>\n<p>Always follow the Design team's specifications for these states and discuss implementation details with them before building. Loading skeletons, error messages, and empty states all have visual implications — do not implement them unilaterally.</p>\n<p>If the component receives data through props, the parent is responsible for passing the correct state down. The component itself should not decide how to fetch or retry — it should only render what it receives.</p>\n<h2 id=\"images\" style=\"position:relative;\"><a href=\"#images\" aria-label=\"images permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Images</h2>\n<p>All images must be in <code>webp</code> or <code>svg</code> format. Do not use <code>png</code> or <code>jpeg</code>. Use <code>svg</code> for icons and illustrations that need to scale without quality loss. Use <code>webp</code> for photographs and raster assets. Always provide meaningful <code>alt</code> text for accessibility.</p>\n<h2 id=\"styling\" style=\"position:relative;\"><a href=\"#styling\" aria-label=\"styling permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Styling</h2>\n<h3 id=\"css\" style=\"position:relative;\"><a href=\"#css\" aria-label=\"css permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>CSS</h3>\n<h4 id=\"file-structure-1\" style=\"position:relative;\"><a href=\"#file-structure-1\" aria-label=\"file structure 1 permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>File Structure</h4>\n<p>Stylesheets are co-located with the component they style. Each component owns exactly one stylesheet file, which defines exactly one BEM block. Styles are written mobile-first: base rules target small screens and larger-screen adjustments are layered with media queries at the standard breakpoints — <code>768px</code> for tablet and <code>1280px</code> for desktop.</p>\n<h4 id=\"colors\" style=\"position:relative;\"><a href=\"#colors\" aria-label=\"colors permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Colors</h4>\n<p>Always use CSS custom properties for colors. Color values are defined as design tokens in Figma and exported to <code>generated-variables.scss</code> (see the <a href=\"#figma\">Figma</a> section). This keeps color themes centralized and supports future additions like dark mode.</p>\n<p>Never use hex color values in stylesheets. This includes bare hex values and hex fallbacks inside <code>var()</code> calls. If a variable is missing, the broken color should be visible so it gets fixed — not silently hidden by a fallback. If a variable does not exist in <code>generated-variables.scss</code>, ask the designer to add it in Figma and re-export.</p>\n<pre><code class=\"language-scss\">// Wrong — bare hex value\nbackground: #fff;\n\n// Wrong — hex fallback inside var(); broken colors must be visible, not hidden\nbackground: var(--neutral-surface-default, #fff);\n\n// Correct — variable only\nbackground: var(--neutral-surface-default);\n</code></pre>\n<h4 id=\"inline-styles\" style=\"position:relative;\"><a href=\"#inline-styles\" aria-label=\"inline styles permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Inline Styles</h4>\n<p>Do not use inline styles in markup — all styling must go through the component's stylesheet.</p>\n<p>The only accepted exception is declaring CSS custom properties inside the base layout primitives (<code>Stack</code>, <code>Box</code>, <code>Grid</code>). This exception does not extend to page or feature components.</p>\n<pre><code class=\"language-tsx\">{\n  /* Wrong — never use inline styles */\n}\n&#x3C;div style={{ color: 'red', marginTop: 16 }}>...&#x3C;/div>;\n\n{\n  /* Correct — express all styling through BEM classes and the stylesheet */\n}\n&#x3C;div className=\"document-card__title document-card__title--error\">...&#x3C;/div>;\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex — wrong: never use inline styles\ndiv(style: { color: \"red\", margin_top: 16 }) { \"...\" }\n\n# Phlex — correct: express all styling through BEM classes and the stylesheet\ndiv(class: \"document-card__title document-card__title--error\") { \"...\" }\n</code></pre>\n<h4 id=\"specificity\" style=\"position:relative;\"><a href=\"#specificity\" aria-label=\"specificity permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Specificity</h4>\n<p>Do not use <code>!important</code>. The only accepted exception is overriding styles from a closed third-party library where no selector-specificity solution is possible.</p>\n<p>Never target another component's classes from inside a different component's stylesheet. Rules like <code>.stack .text {}</code> are forbidden — each component is responsible for its own styles only and must never reach into or depend on another component's context.</p>\n<p>Avoid overly complex selectors. In particular, avoid <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/:has\"><code>:has()</code></a> selectors with deep or broad matches — they are evaluated continuously by the browser and can cause severe layout performance issues in Safari. If you need <code>:has()</code>, keep it shallow and scoped tightly to the component root.</p>\n<h3 id=\"bem\" style=\"position:relative;\"><a href=\"#bem\" aria-label=\"bem permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>BEM</h3>\n<p>Every styled element must carry a <a href=\"https://getbem.com/\">BEM</a> class (<a href=\"https://en.bem.info/methodology/naming-convention/\">naming convention reference</a>). Do not target bare HTML tags like <code>p</code>, <code>li</code>, or <code>h2</code> in stylesheets.</p>\n<p>BEM classes follow the pattern <code>block__element--modifier</code>:</p>\n<ul>\n<li><strong>Block</strong> — the component root, e.g. <code>.card</code></li>\n<li><strong>Element</strong> — a part of the block, connected with <code>__</code>, e.g. <code>.card__title</code></li>\n<li><strong>Modifier</strong> — a variant or state, connected with <code>--</code>, e.g. <code>.card__title--featured</code></li>\n</ul>\n<pre><code class=\"language-scss\">// Correct\n.card { ... }\n.card__title { ... }\n.card__title--featured { ... }\n\n// Wrong — deep nesting\n.card__body__title { ... }\n\n// Wrong — bare HTML element selector\n.card p { ... }\n</code></pre>\n<p>A few rules to keep in mind:</p>\n<ul>\n<li>Elements belong to the block, not to other elements. <code>.card__title</code> is correct even if the title sits inside <code>.card__body</code>. Never write <code>.card__body__title</code>.</li>\n<li>A modifier class must always appear alongside its base class: <code>class=\"card__title card__title--featured\"</code>. Never use a modifier on its own.</li>\n<li>Do not promote a sub-element into its own block just because it is visually nested. Only extract something into its own block if it is genuinely reused independently in other contexts.</li>\n</ul>\n<h3 id=\"mobile\" style=\"position:relative;\"><a href=\"#mobile\" aria-label=\"mobile permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Mobile</h3>\n<p>Mobile styles follow from the mobile-first base rules, but a few patterns apply specifically to small-screen layouts:</p>\n<ul>\n<li><strong>Touch targets</strong> — a minimum of 44×44px is a good baseline for buttons, links, and any tappable region. If the visible element is smaller, adding padding is usually preferable to expanding the visual size. Confirm the exact sizing with the Design team before implementing. All inputs must have a minimum font size of 16px to prevent automatic zoom on iOS.</li>\n<li><strong>Full-width targets</strong> — prefer full-width buttons and list items on mobile. Narrow tap targets on small screens lead to mis-taps.</li>\n<li><strong>Safe-area insets</strong> — when the layout extends to the screen edge, account for device notches and home indicators using <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/env\"><code>env(safe-area-inset-*)</code></a>. This requires <code>viewport-fit=cover</code> in the viewport meta tag.</li>\n<li><strong>Full-bleed content</strong> — when content needs to extend to the screen edge but lives inside a padded container, use CSS subgrid to let the item opt out of the container's column padding rather than applying negative margins. Negative margins work but break with nesting; subgrid composes cleanly.</li>\n</ul>\n<h2 id=\"accessibility\" style=\"position:relative;\"><a href=\"#accessibility\" aria-label=\"accessibility permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Accessibility</h2>\n<p>Accessibility is a first-class requirement, not a post-launch checklist item. Build it in from the start.</p>\n<ul>\n<li>\n<p><strong>Semantic HTML</strong> — use the correct element for the job. Buttons trigger actions; links navigate. Do not use <code>div</code> or <code>span</code> as interactive elements without the appropriate <a href=\"https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles\">ARIA role</a>. The <a href=\"#text\">Text</a> component accepts an <code>element</code> prop so visual style and semantic element can be chosen independently.</p>\n</li>\n<li>\n<p><strong>ARIA labels</strong> — icon-only buttons must have a label that describes the action. Decorative images and icons should use <code>aria-hidden=\"true\"</code> so screen readers skip them.</p>\n<p>Since <code>IconButton</code> never has visible text children, <code>label</code> is always a required prop. Use <code>label</code> as the prop name and map it to <code>aria-label</code> internally:</p>\n<pre><code class=\"language-tsx\">// IconButton always requires a label — it has no visible text children\ninterface IconButtonProps {\n  icon: string;\n  label: string;\n}\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex — props declared with the literal gem\nprop :icon, String\nprop :label, String\n</code></pre>\n</li>\n<li>\n<p><strong>Focus management</strong> — use <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible\"><code>:focus-visible</code></a> rather than <code>:focus</code> to show focus rings only for keyboard users. Never suppress the focus outline entirely. When a modal or dialog opens, move focus into it; when it closes, return focus to the trigger.</p>\n</li>\n<li>\n<p><strong>Hover states</strong> — wrap hover styles in <a href=\"https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover\"><code>@media (hover: hover)</code></a> so they only apply on devices that support pointer hover. Touch-only devices should never receive hover styles.</p>\n</li>\n<li>\n<p><strong>Keyboard navigation</strong> — all interactive elements must be reachable and operable with a keyboard. Verify tab order is logical. Custom interactive components (dropdowns, dialogs) must handle <code>Escape</code> to close and arrow keys where appropriate.</p>\n</li>\n<li>\n<p><strong>Visually hidden text</strong> — use a <code>.visually-hidden</code> CSS utility class (not <code>display: none</code> or <code>visibility: hidden</code>) to provide context for screen readers without affecting the visual layout.</p>\n</li>\n<li>\n<p><strong>Color contrast</strong> — text and meaningful UI elements must meet <a href=\"https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html\">WCAG AA contrast ratios</a>. Do not communicate state through color alone.</p>\n</li>\n</ul>\n<h2 id=\"figma\" style=\"position:relative;\"><a href=\"#figma\" aria-label=\"figma permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Figma</h2>\n<p>Figma is the source of truth for all visual design. Text sizes and colors are defined there as variables. To generate the corresponding CSS custom property rules, use the <a href=\"https://github.com/nerdgeschoss/figma-export-variables-plugin\">figma-export-variables-plugin</a>:</p>\n<ol>\n<li>Clone the plugin repository and build it locally.</li>\n<li>Open Figma in the <strong>native macOS app</strong> (the web app does not support local plugins).</li>\n<li>Go to <strong>Plugins → Development → Import plugin from manifest</strong>.</li>\n<li>Select the built <code>manifest.json</code> file.</li>\n<li>Run the plugin from the Plugins menu whenever design tokens change to keep CSS variables in sync. Never hardcode color or typography values that should come from the design tokens.</li>\n</ol>\n<p>Components in Figma are designed at three breakpoints: mobile, tablet, and desktop. Always implement from the smallest breakpoint upward, adding adjustments for larger screens as you go. This keeps styles mobile-first by default and avoids having to override rules downward.</p>\n<h2 id=\"localization\" style=\"position:relative;\"><a href=\"#localization\" aria-label=\"localization permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Localization</h2>\n<p>All user-facing text must come from locale files — never hardcode strings directly into markup. This applies to labels, button text, headings, error messages, and any other copy visible to the user.</p>\n<p>The Figma file for the screen is the source of truth for all copy. Always take text content from the Figma design for that screen, not from assumptions or placeholder text.</p>\n<pre><code class=\"language-tsx\">{\n  /* Correct */\n}\n&#x3C;Text type=\"body\">{i18n.t('dashboard.welcome_message')}&#x3C;/Text>;\n\n{\n  /* Wrong — hardcoded string */\n}\n&#x3C;Text type=\"body\">Welcome back!&#x3C;/Text>;\n</code></pre>\n<pre><code class=\"language-ruby\"># Phlex — correct\ntext(type: \"body\") { t(\"dashboard.welcome_message\") }\n\n# Phlex — wrong: hardcoded string\ntext(type: \"body\") { \"Welcome back!\" }\n</code></pre>\n<p>Locale keys are organized by feature using namespaces, for example <code>navigation.dashboard</code> or <code>dashboard.current_month_overview</code>. Leaf keys use <code>lower_snake_case</code> and should be named after the meaning of the text, not its literal content.</p>\n<p>Each page must implement its own copy — do not use generic or shared translations. Sharing a key across multiple screens makes it impossible to update copy for one page without affecting others.</p>\n<p>Keys must be derived from the copy they represent, not numbered sequentially:</p>\n<pre><code class=\"language-yaml\"># Wrong\nparagraph_1: 'Are you sure you want to delete this item?'\nparagraph_2: 'This action cannot be undone.'\nparagraph_3: 'Confirm deletion'\n\n# Correct\nare_you_sure_you_want_to_delete_this_item: 'Are you sure you want to delete this item?'\nthis_action_cannot_be_undone: 'This action cannot be undone.'\nconfirm_deletion: 'Confirm deletion'\n</code></pre>\n<p>When adding a new piece of UI text, add the key to the locale source file before using it in the component. Consult the project's own documentation for the canonical location of that file.</p>\n<h2 id=\"formatting\" style=\"position:relative;\"><a href=\"#formatting\" aria-label=\"formatting permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Formatting</h2>\n<p>Use a <code>Formatter</code> class for currency, percentages, dates, and datetimes. Never format these values inline or with hand-rolled string manipulation. The class is carried over from project to project — check an existing project for the canonical source before implementing from scratch.</p>\n<p>In React, instantiate it with the active locale (typed to the project's <code>Locale</code> type from <code>i18n.ts</code>) so all output stays consistent with the user's language settings. The class wraps the <code>Intl</code> APIs and handles null/invalid inputs gracefully.</p>\n<pre><code class=\"language-tsx\">export class Formatter {\n  currency(value: number | string): string {}\n  percentage(value: number): string {}\n  date(value: Date | string): string | null {}\n  datetime(value: Date | string): string | null {}\n}\n</code></pre>\n<p>In Rails, use the built-in i18n helpers:</p>\n<pre><code class=\"language-ruby\">number_to_currency(invoice.total, unit: '€')\nnumber_to_percentage(value, precision: 1)\nl(invoice.due_date, format: :long)\nl(invoice.created_at, format: :long)\n</code></pre>\n<h2 id=\"component-structure\" style=\"position:relative;\"><a href=\"#component-structure\" aria-label=\"component structure permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Component Structure</h2>\n<p>Each component in React is a single exported function declared with <code>function</code>, not an arrow function. The file exports exactly one component, named identically to the file. The return type must be declared explicitly as <code>ReactElement</code>. Do not use <code>React.FC</code>.</p>\n<p>Phlex follows the same one-component-per-file rule. Props are declared with the <code>literal</code> gem rather than a TypeScript <code>interface</code>.</p>\n<pre><code class=\"language-tsx\">interface Props {\n  title: string;\n  url: string;\n}\n\nexport function DocumentCard({ title, url }: Props): ReactElement {\n  return (\n    &#x3C;Box padding={{ vertical: 16, horizontal: 24 }}>\n      &#x3C;Text type=\"caption-bold\">{title}&#x3C;/Text>\n    &#x3C;/Box>\n  );\n}\n</code></pre>\n<h3 id=\"props-and-typing\" style=\"position:relative;\"><a href=\"#props-and-typing\" aria-label=\"props and typing permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Props and Typing</h3>\n<p>Props must always be declared with an explicit <code>interface</code>. Declare only the specific fields the component needs — prefer <code>Pick</code> over accepting a full model type.</p>\n<pre><code class=\"language-tsx\">// Wrong — accepts the entire User object when only two fields are needed\ninterface Props {\n  user: User;\n}\n\n// Correct — declare only what is actually used\ninterface Props {\n  user: Pick&#x3C;User, 'name' | 'email'>;\n}\n</code></pre>\n<p>In Phlex, use <code>prop :field, Type</code> from the <code>literal</code> gem and declare only the fields the component needs — the same principle applies.</p>\n<pre><code class=\"language-ruby\">prop :name, String\nprop :email, String\n</code></pre>\n<h3 id=\"useeffect\" style=\"position:relative;\"><a href=\"#useeffect\" aria-label=\"useeffect permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>useEffect</h3>\n<p>Avoid <code>useEffect</code> unless there is no other option. Common component-level misuses to avoid:</p>\n<ul>\n<li>Synchronizing two pieces of React state — derive the second value instead</li>\n<li>Values that can be computed from existing state or props — use a derived variable or <code>useMemo</code></li>\n</ul>\n<p>Before reaching for <code>useEffect</code>, ask whether the same outcome can be achieved without it.</p>","frontmatter":{"title":"Components","shortTitle":"Components","breadcrumbs":[{"title":"Handbook","path":"/handbook"},{"title":"n.U.T.S","path":"/handbook/nuts"}]},"headings":[{"value":"Presentational (Dumb) Components","depth":2},{"value":"File Structure","depth":3},{"value":"Primitives","depth":3},{"value":"Grid","depth":4},{"value":"Icon","depth":4},{"value":"Stack","depth":4},{"value":"Box","depth":4},{"value":"Collapse","depth":4},{"value":"Text","depth":4},{"value":"States","depth":3},{"value":"Images","depth":2},{"value":"Styling","depth":2},{"value":"CSS","depth":3},{"value":"File Structure","depth":4},{"value":"Colors","depth":4},{"value":"Inline Styles","depth":4},{"value":"Specificity","depth":4},{"value":"BEM","depth":3},{"value":"Mobile","depth":3},{"value":"Accessibility","depth":2},{"value":"Figma","depth":2},{"value":"Localization","depth":2},{"value":"Formatting","depth":2},{"value":"Component Structure","depth":2},{"value":"Props and Typing","depth":3},{"value":"useEffect","depth":3}]}},"pageContext":{}},
    "staticQueryHashes": ["63159454"]}