add progressive enhancement guidelines to CLAUDE.md
All checks were successful
deploy / deploy (push) Successful in 59s
All checks were successful
deploy / deploy (push) Successful in 59s
Document the CSS :has(:checked) pattern for forms with dynamic sections, noscript fallbacks for JS-only elements, and the HEEx <style> raw text gotcha. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
db130a7155
commit
e24f9aa4f3
42
CLAUDE.md
42
CLAUDE.md
@ -177,6 +177,48 @@ socket |> assign(form: to_form(changeset))
|
||||
- Avoid LiveComponents unless you have a specific need (isolated state, targeted updates)
|
||||
- Never use deprecated `phx-update="append"` or `phx-update="prepend"`
|
||||
|
||||
## Progressive Enhancement
|
||||
|
||||
Build HTML+CSS that works without JS. LiveView layers on top.
|
||||
|
||||
**Principle:** If a state change is purely visual (which radio is selected, which panel is shown), handle it in CSS. Don't round-trip to the server for something the browser can do alone.
|
||||
|
||||
**Pattern for forms with dynamic sections:**
|
||||
|
||||
1. Render ALL possible states in the HTML (e.g. all adapter configs, all option panels)
|
||||
2. Use CSS `:has(:checked)` to show the matching section and hide the rest
|
||||
3. Namespace field names to avoid clashes (`email[brevo][api_key]`, not `email[api_key]`)
|
||||
4. Give the form both `action` (no-JS POST) and `phx-submit` (LiveView)
|
||||
5. Add a thin controller to handle the no-JS POST path
|
||||
|
||||
```css
|
||||
/* Hide all sections by default, show the one matching the checked radio */
|
||||
.config-section[data-option] { display: none; }
|
||||
form:has(#option-foo:checked) [data-option="foo"] { display: block; }
|
||||
```
|
||||
|
||||
**JS-only elements:** Wrap in a container with an ID, hide it via `<noscript><style>`, and provide a form POST alternative:
|
||||
|
||||
```heex
|
||||
<span id="async-action">
|
||||
<.button type="button" phx-click="do_thing">Do thing</.button>
|
||||
</span>
|
||||
<noscript>
|
||||
<style>#async-action { display: none; }</style>
|
||||
<.form for={%{}} action={~p"/thing"} method="post" style="display:inline">
|
||||
<.button>Do thing</.button>
|
||||
</.form>
|
||||
</noscript>
|
||||
```
|
||||
|
||||
**HEEx and `<style>` tags:** HEEx treats `<style>` content as raw text (no `{@var}` interpolation). To output dynamic CSS, use a helper that returns `Phoenix.HTML.raw/1`:
|
||||
|
||||
```elixir
|
||||
defp dynamic_style(value) do
|
||||
Phoenix.HTML.raw("<style>.thing[data-x=\"#{value}\"] { display: block; }</style>")
|
||||
end
|
||||
```
|
||||
|
||||
## JS/CSS Guidelines
|
||||
|
||||
- Project is fully Tailwind-free — hand-written CSS with `@layer`, native nesting, `oklch()`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user