[DOC] skills: update odoo-javascript skill documentation

This commit is contained in:
GitHub Copilot 2026-08-15 13:00:56 +02:00
parent 158933e96e
commit 162ba0b570
6 changed files with 875 additions and 1 deletions

View file

@ -0,0 +1,182 @@
---
name: odoo-web-design
description: Professional finish for the Odoo frontend of this repo — visual polish, responsive layout and WCAG accessibility across CSS, QWeb/HTML and JS. Use when styling or reviewing website/portal pages (website_sale_aplicoop), adding UI components, or fixing responsive/a11y/design-consistency issues.
metadata:
type: reference
---
# Web design & finish (Odoo 18 frontend)
Covers the three layers together: `static/src/css/`, `static/src/js/`, and the QWeb templates in
`views/*.xml`. For language-level conventions see `odoo-qweb-html`, `odoo-javascript`,
`odoo-xml-views`; this skill is about the **finish**: does it look designed, does it work at 320 px,
does it work without a mouse.
## Inherited non-negotiables
- No logic in QWeb — prepare everything in the controller.
- No business logic in JS — UI and events only.
- **No inline `style="..."`** in templates. A style needs a class, and the class needs a token.
- Every user-visible string is translatable (see §4).
## 1. Work order
Never start by editing a component. Always:
1. **Audit** with the commands in §7. Write down what is inconsistent before touching anything.
2. **Tokens first** (`base/variables.css`). Every value a component needs must already exist as a token.
3. **Component by component**, one file each, media queries co-located at the bottom of that file.
4. **Accessibility pass** per template, top to bottom.
5. **Verify** — lint, contrast, keyboard-only, 320 px, 200 % zoom.
Doing 3 before 2 is how a codebase ends up with 61 `!important` and three shades of the same blue.
## 2. Design tokens
Odoo 18 ships Bootstrap 5.3 with `$variable-prefix: ''`
(`/media/snt/data/odoo/ocb/addons/web/static/src/scss/bootstrap_overridden.scss:51`), so Bootstrap's
root custom properties are emitted **unprefixed** into `web.assets_frontend` (via
`import_bootstrap.scss``@import "root"`). Available in plain CSS, no SCSS needed:
```
--primary --secondary --success --danger --warning --info --light --dark
--body-color --body-bg --emphasis-color --secondary-bg --tertiary-bg --border-color
--border-radius --border-radius-sm --border-radius-lg --border-radius-xl --border-radius-pill
--link-color --link-hover-color --focus-ring-color --focus-ring-width
--box-shadow --box-shadow-sm --box-shadow-lg --font-sans-serif --gray-100 … --gray-900
```
Rules:
- **Alias, don't copy.** `--ac-color-primary: var(--primary, #007bff);` keeps the website theme
editor in control of the palette. A hardcoded brand hex breaks theming silently.
- **Hex literals live only in `base/variables.css`.** A hex anywhere else is a defect.
- **Prefix new tokens** (`--ac-…`, aplicoop). Bare names like `--primary-color` sit in Bootstrap's
unprefixed namespace and will collide as Odoo adds tokens.
- **Never redefine a token inside a media query** to shrink it — use `clamp()` so it scales
continuously instead of jumping at one width.
- Space and radius come from the scale, never from a typed-in number. If you need a value the scale
doesn't have, the scale is wrong — fix the scale.
## 3. Responsive
- **Mobile-first, `min-width` only.** Bootstrap 5 breakpoints exclusively: **576 / 768 / 992 / 1200 /
1400**. No 480/720/1600 one-offs — a private breakpoint is a design decision nobody else knows about.
- **One range per rule.** Pairing `min-width` and `max-width` to carve a band is almost always a
symptom of desktop-first thinking leaking in.
- **Media queries belong with their component**, at the bottom of its file. A central
`responsive.css` drifts out of sync with the components it overrides and forces `!important`.
Don't add to it; empty it as you touch each component.
If a rule in `responsive.css` spans multiple components or has no clear owner, move it to the
relevant layout partial (e.g. `layout/grid.css`) rather than duplicating it per component; note
any such moves in the audit output.
- **Prefer intrinsic layout to breakpoints**: `repeat(auto-fill, minmax(min(100%, 16rem), 1fr))`,
`clamp()`, `flex-wrap`. Most grids need zero media queries.
- `rem` for everything; `px` only for hairlines, borders and shadow offsets.
- Touch targets: **24×24 px is the AA floor** (WCAG 2.2 §2.5.8); **44×44 px is §2.5.5, level AAA**,
and the size a thumb actually needs — apply it under `@media (pointer: coarse)`. Quantity steppers
and icon buttons are the usual offenders.
- Must survive **320 px wide** and **200 % zoom** with no horizontal scroll.
Details and recipes: `references/responsive.md`.
## 4. Accessibility — WCAG 2.2 AA baseline
- **Semantics before ARIA**: `<button>`, `<nav>`, `<ul>`, `<label for>`. ARIA only when no element
expresses the meaning. A `<div onclick>` is never acceptable.
- **Contrast**: 4.5:1 body text, 3:1 for ≥24 px (or bold ≥19 px), 3:1 for form-control borders,
icon-only buttons and focus rings.
- **Focus**: never `outline: none` without a replacement. Style `:focus-visible`, not `:focus` — the
latter also fires on mouse click and gets removed out of frustration.
- Every input has a real `<label>`. A placeholder is not a label.
- Icon-only control → translatable `aria-label`, or `.visually-hidden` text (Bootstrap ships the class).
- Dynamic updates (cart total, quantity, result count) → `aria-live="polite"` region.
- Images: meaningful `alt`, or `alt=""` when decorative. Never omit the attribute.
- Status is never conveyed by colour alone — pair it with text or an icon.
- Every animation you add needs a `@media (prefers-reduced-motion: reduce)` escape.
**Translatable QWeb attributes** (auto-extracted, `odoo/tools/translate.py:163`), plus their
`t-attf-` forms:
```
string help placeholder alt title label data-tooltip confirm
aria-label aria-keyshortcuts aria-placeholder aria-roledescription aria-valuetext
```
`aria-description` is **not** translated — use `aria-label` or visually-hidden text instead.
Strings injected from JS go through `window.i18nManager.get(key)` (served by `/eskaera/i18n`), with
the key added server-side — never a hardcoded literal, including in live-region announcements.
Per-component patterns: `references/accessibility.md`.
## 5. JS for finish (UI only)
- **Delegated events** — content is lazy-loaded and infinite-scrolled, direct listeners die.
- After injecting nodes: move or restore focus deliberately, announce the new count in the live
region, and set `aria-busy` on the container while fetching.
- Check `matchMedia("(prefers-reduced-motion: reduce)")` before animating or smooth-scrolling.
- Toggle **classes**, don't write `element.style.*` — inline styles from JS are the same defect as
inline styles in templates, just later.
- Debounce search input ≥ 200 ms and announce "N results" politely.
- Batch DOM reads and writes; never read `offsetHeight` inside a loop that also writes.
## 6. Asset delivery
`website_sale.css` pulls its 17 partials with `@import`. Odoo rewrites those to absolute URLs and
hoists them to the top of the bundle (`odoo/addons/base/models/assetsbundle.py:909` and `:525`), so
they ship as ~17 extra render-blocking requests **outside** the minified bundle. For a production
finish, prefer listing each file in the manifest's `web.assets_frontend` (order: variables →
utilities → layout → components → sections). Only rename them to `.scss` if the maintainer
explicitly requests SCSS compilation so Odoo inlines the imports at compile time.
## 7. Audit & verify
If `/media/snt/data/addons-cm/website_sale_aplicoop` does not exist, run the audit commands from the
repository root of the current workspace instead, and report which path was used.
```bash
cd /media/snt/data/addons-cm/website_sale_aplicoop
# Breakpoint sprawl — should only ever list 576/768/992/1200/1400, all min-width
grep -rho "@media[^{]*" static/src/css | sort | uniq -c | sort -rn
# Hardcoded colours outside the token file (expect: none)
grep -rn "#[0-9a-fA-F]\{3,8\}\b" static/src/css --include="*.css" | grep -v "base/variables.css"
# Specificity debt
grep -rc "!important" static/src/css/*/*.css | grep -v ':0'
# Focus handling: :focus-visible should dominate, outline:none must be paired with a replacement
grep -rn ":focus-visible\|outline: *none\|outline: *0" static/src/css
# Motion safety (expect at least one per animating component)
grep -rn "prefers-reduced-motion" static/src/css
# Inline styles in templates (expect: none)
grep -rn 'style="' views/*.xml
# Images without alt
grep -o "<img[^>]*" views/*.xml | grep -v "alt="
# Interactive divs/spans (expect: none)
grep -rn "<div[^>]*t-on-click\|<span[^>]*t-on-click\|<div[^>]*onclick" views/*.xml
```
Then, always:
```bash
pre-commit run --all-files # prettier (width 100, indent 4) over css/js/xml + eslint
docker-compose up -d # http://localhost:8070 — check at 320/768/1280 and 200% zoom
```
Manual passes that no grep replaces: **Tab through the whole page** (visible focus everywhere, order
matches visual order, no trap), and check contrast on the actual rendered colours.
## 8. References
- `references/accessibility.md` — WCAG AA patterns as QWeb snippets, per component.
- `references/responsive.md` — breakpoints, intrinsic layout, images, test matrix.
- `references/css-architecture.md` — file layout, naming, specificity, killing `!important`.
- `references/finish-checklist.md` — the polish pass: states, rhythm, empty/loading/error.
Repo docs: `docs/QWEB_BEST_PRACTICES.md`, `docs/LAZY_LOADING.md`.