|
6 | 6 | ] |
7 | 7 | ) %> |
8 | 8 |
|
9 | | -<p class="mt-4">Use <code>content_for :l_ui_head</code> to inject content into the layout <code><head></code>, after the stylesheet. This is the right place for dynamic <code><style></code> blocks, preload hints, or other per-request head content.</p> |
10 | | - |
11 | | -<h2 class="mt-8">Dynamic theme tokens</h2> |
12 | | - |
13 | | -<p class="mt-4">Override CSS custom properties per request (e.g. per-tenant branding) by injecting a <code><style></code> block. Placing it after the stylesheet ensures the cascade override takes effect correctly.</p> |
| 9 | +<p class="mt-4">Use <code>content_for :l_ui_head</code> to inject arbitrary content into the layout <code><head></code>, after the stylesheet. This is the place for third-party scripts (analytics, chat widgets), a page-specific inline <code><script></code>, meta and verification tags, preload hints, or a per-request stylesheet link - anything you would normally add to the head of a page. For styling, the overrides file is usually a better fit (see below).</p> |
14 | 10 |
|
15 | 11 | <pre class="l-ui-surface mt-4"><code><% content_for :l_ui_head do %> |
16 | | - <style> |
17 | | - :root { --accent: <%= @tenant.accent_color %>; --accent-foreground: oklch(1 0 0); } |
18 | | - </style> |
| 12 | + <%= javascript_include_tag "https://cdn.example.com/widget.js", defer: true %> |
| 13 | + <meta name="google-site-verification" content="..."> |
19 | 14 | <% end %></code></pre> |
20 | 15 |
|
21 | | -<p class="l-ui-notice l-ui-notice--warning mt-4"><strong>Security:</strong> never interpolate user-supplied strings directly into a <code><style></code> tag - this allows CSS injection. Validate or sanitise any user-derived values before interpolation.</p> |
22 | | - |
23 | | -<p class="mt-4">See the <%= link_to "colors", layout_colors_path %> page for the full list of design tokens.</p> |
| 16 | +<p class="l-ui-notice mt-4"><strong>Where styles fit best.</strong> layered-ui token and component overrides (e.g. <code>--accent</code>, restyling a <code>.l-ui-*</code> class) fit in <code>app/assets/tailwind/layered_ui_overrides.css</code> - see the <%= link_to "colors", layout_colors_path %> page. Other custom styling fits in your app's own application stylesheet, like any normal Rails app. Both keep styles with the rest of your CSS, where they are easier to maintain than inline in the head.</p> |
24 | 17 |
|
25 | | -<h2 class="mt-8">CSP compatibility</h2> |
| 18 | +<h2 class="mt-8">Per-tenant theming</h2> |
26 | 19 |
|
27 | | -<p class="mt-4">Inline <code><style></code> blocks are blocked by a strict <code>Content-Security-Policy: style-src 'self'</code> header. If your app enforces a strict CSP, add a nonce using Rails' <code>content_security_policy_nonce</code> helper - Rails includes the matching nonce in the CSP header automatically:</p> |
| 20 | +<p class="mt-4">When brand colors vary per request and cannot be known at build time, a good option is to serve them as a dedicated stylesheet from a Rails controller and link it here. The stylesheet overrides the design tokens, and this approach is Turbo- and CSP-friendly while keeping styling out of the markup.</p> |
28 | 21 |
|
29 | 22 | <pre class="l-ui-surface mt-4"><code><% content_for :l_ui_head do %> |
30 | | - <style nonce="<%= content_security_policy_nonce %>"> |
31 | | - :root { --accent: <%= @tenant.accent_color %>; --accent-foreground: oklch(1 0 0); } |
32 | | - </style> |
| 23 | + <%= stylesheet_link_tag tenant_theme_path(current_tenant) %> |
33 | 24 | <% end %></code></pre> |
34 | 25 |
|
35 | | -<h2 class="mt-8">Turbo Drive compatibility</h2> |
| 26 | +<p class="mt-4">The controller behind <code>tenant_theme_path</code> renders CSS that overrides the design tokens, for example <code>:root { --accent: <%= @tenant.accent_color %>; --accent-foreground: oklch(1 0 0); }</code>.</p> |
| 27 | + |
| 28 | +<p class="l-ui-notice l-ui-notice--warning mt-4"><strong>Security:</strong> never interpolate user-supplied strings directly into the served CSS - this allows CSS injection. Validate or sanitise any user-derived values before interpolation.</p> |
36 | 29 |
|
37 | | -<p class="mt-4">Turbo Drive merges <code><head></code> content on each navigation - changed <code><style></code> blocks are replaced correctly. The caveat is the preview pass: when Turbo restores a snapshot from its cache before the network response arrives, a cached snapshot may briefly show stale tokens.</p> |
| 30 | +<h2 class="mt-8">CSP and Turbo compatibility</h2> |
38 | 31 |
|
39 | | -<p class="mt-4">If tokens are stable for a session (e.g. a single-tenant session where branding only changes on login), add <code>data-turbo-track="reload"</code> to the <code><style></code> tag. Turbo will trigger a full page reload only when the tag content changes between visits.</p> |
| 32 | +<p class="mt-4">The linked-stylesheet pattern above sidesteps two issues that an inline <code><style></code> block runs into. A stylesheet served from your own origin satisfies a strict <code>Content-Security-Policy: style-src 'self'</code> with no nonce, and Turbo caches and reuses it by URL - both reasons to prefer it.</p> |
40 | 33 |
|
41 | | -<p class="mt-4">If tokens vary per navigation (e.g. a user switches between tenants mid-session), <code>data-turbo-track="reload"</code> will reload on every visit, effectively disabling Turbo. The preferred pattern in that case is to serve the theme as a dedicated stylesheet URL:</p> |
| 34 | +<p class="mt-4">If you do inject an inline <code><style></code> block instead, two caveats apply. Under a strict CSP, add a nonce with Rails' <code>content_security_policy_nonce</code> helper - Rails includes the matching nonce in the CSP header automatically:</p> |
42 | 35 |
|
43 | 36 | <pre class="l-ui-surface mt-4"><code><% content_for :l_ui_head do %> |
44 | | - <link rel="stylesheet" href="<%= @tenant.theme_stylesheet_url %>"> |
| 37 | + <style nonce="<%= content_security_policy_nonce %>"> |
| 38 | + :root { --accent: <%= @tenant.accent_color %>; --accent-foreground: oklch(1 0 0); } |
| 39 | + </style> |
45 | 40 | <% end %></code></pre> |
46 | 41 |
|
47 | | -<p class="mt-4">Turbo caches and reuses stylesheet responses by URL, so this approach is both Turbo-friendly and avoids inline style blocks entirely.</p> |
| 42 | +<p class="mt-4">And on Turbo's preview pass - when it restores a cached snapshot before the network response arrives - an inline block may briefly show stale tokens. If tokens are stable for a session, add <code>data-turbo-track="reload"</code> so Turbo reloads only when the block changes between visits; if they vary per navigation this disables Turbo, so the linked stylesheet is the better fit.</p> |
0 commit comments