160 lines
5.9 KiB
Markdown
160 lines
5.9 KiB
Markdown
# CSS architecture
|
|
|
|
## File layout
|
|
|
|
```
|
|
static/src/css/
|
|
├── website_sale.css # index only: @import list, no rules of its own
|
|
├── base/
|
|
│ ├── variables.css # :root tokens — the ONLY file with hex literals
|
|
│ └── utilities.css # tiny single-purpose helpers, when Bootstrap lacks one
|
|
├── layout/
|
|
│ ├── pages.css # page shells, containers, backgrounds
|
|
│ └── header.css
|
|
├── components/ # one file per reusable component
|
|
│ ├── product-card.css
|
|
│ ├── order-card.css
|
|
│ ├── cart.css
|
|
│ ├── buttons.css
|
|
│ ├── quantity-control.css
|
|
│ ├── forms.css
|
|
│ ├── alerts.css
|
|
│ └── tag-filter.css
|
|
└── sections/ # page-specific composition only
|
|
├── products-grid.css
|
|
├── order-list.css
|
|
├── checkout.css
|
|
└── info-cards.css
|
|
```
|
|
|
|
Rules of placement:
|
|
|
|
- A rule that could appear on two pages is a **component**, never a section.
|
|
- A **section** file may only position and space components — if it restyles a component's internals,
|
|
the component is missing a modifier.
|
|
- Each component file owns its own media queries, at the bottom. There is no global responsive file
|
|
in a healthy tree; a legacy `layout/responsive.css` should shrink to zero as components are touched.
|
|
- `website_sale.css` contains imports and comments only.
|
|
|
|
## Naming
|
|
|
|
Prefix everything with `eskaera-` (the product name already used for page-level classes). Unprefixed
|
|
names like `.product-card`, `.cart-header`, `.qty-control` sit in the same namespace as Bootstrap,
|
|
Odoo core and every other installed addon — that collision is what forces `!important` later.
|
|
|
|
```
|
|
.eskaera-product-card /* block */
|
|
.eskaera-product-card__title /* element */
|
|
.eskaera-product-card--out-of-stock /* modifier */
|
|
.is-loading .is-active .has-error /* state, always paired with a block class */
|
|
```
|
|
|
|
`js-` prefix for hooks JS queries, and never style a `js-` class — that way markup can be restyled
|
|
without breaking behaviour, and behaviour can move without breaking styles.
|
|
|
|
When renaming existing classes, do it component by component: template + CSS + JS selectors + tests
|
|
in the same commit, then grep for the old name across `views/`, `static/src/`, and `README*`.
|
|
|
|
## Specificity — how to stop writing `!important`
|
|
|
|
Overriding Bootstrap globally (`.list-group-item { … }`, `.alert-warning { … }`) is a fight you win
|
|
only with `!important`, and it leaks into every other addon. Scope the override under the page root
|
|
instead: equal specificity plus one class always wins, no escape hatch needed.
|
|
|
|
```css
|
|
/* ❌ global override, needs !important to beat Bootstrap's own later rules */
|
|
.list-group-item {
|
|
border-radius: 0 !important;
|
|
}
|
|
|
|
/* ✅ scoped: higher specificity, no !important, no leakage */
|
|
.eskaera-checkout-page .list-group-item {
|
|
border-radius: 0;
|
|
}
|
|
```
|
|
|
|
Other rules:
|
|
|
|
- Max **3** levels of nesting in a selector. Deeper means the markup is doing the work the class
|
|
should do.
|
|
- No ID selectors for styling; IDs are for anchors and `aria-*` references.
|
|
- No element selectors outside `base/` (`div.product-card` locks the markup).
|
|
- `!important` is allowed in exactly two places: the `prefers-reduced-motion` reset, and a documented
|
|
override of a core rule you cannot scope — with a comment saying which rule and why.
|
|
|
|
## Ordering inside a file
|
|
|
|
```css
|
|
.eskaera-product-card {
|
|
/* 1. layout */
|
|
display: flex;
|
|
flex-direction: column;
|
|
/* 2. box */
|
|
padding: var(--ac-space-md);
|
|
border: 1px solid var(--ac-border-light);
|
|
border-radius: var(--ac-radius-lg);
|
|
/* 3. typography */
|
|
font-size: var(--ac-text-base);
|
|
color: var(--ac-text-primary);
|
|
/* 4. paint & motion */
|
|
background: var(--ac-surface);
|
|
box-shadow: var(--ac-shadow-sm);
|
|
transition: box-shadow var(--ac-transition-fast);
|
|
}
|
|
```
|
|
|
|
Then, in order: `:hover` (inside `@media (hover: hover)`), `:focus-visible`, `:active`, `:disabled`,
|
|
state classes, modifiers, media queries.
|
|
|
|
## Tokens
|
|
|
|
Full rules in `SKILL.md` §2. In short:
|
|
|
|
```css
|
|
:root {
|
|
/* alias Odoo/Bootstrap's unprefixed custom properties so theming still works */
|
|
--ac-color-primary: var(--primary, #007bff);
|
|
--ac-color-danger: var(--danger, #dc3545);
|
|
--ac-surface: var(--body-bg, #fff);
|
|
--ac-text-primary: var(--body-color, #1a202c);
|
|
--ac-border-light: var(--border-color, #e2e8f0);
|
|
|
|
/* own scales */
|
|
--ac-space-xs: 0.25rem;
|
|
--ac-space-sm: 0.5rem;
|
|
--ac-space-md: 1rem;
|
|
--ac-space-lg: 1.5rem;
|
|
--ac-space-xl: 2rem;
|
|
|
|
--ac-radius-sm: 0.25rem;
|
|
--ac-radius-md: 0.5rem;
|
|
--ac-radius-lg: 0.75rem;
|
|
|
|
--ac-shadow-sm: 0 2px 8px rgb(0 0 0 / 8%);
|
|
--ac-shadow-md: 0 4px 12px rgb(0 0 0 / 10%);
|
|
|
|
--ac-transition-fast: 200ms ease;
|
|
}
|
|
```
|
|
|
|
Migrating a legacy token set: add the `--ac-` token aliased to the old name, move consumers file by
|
|
file, delete the old token when `grep -rn "\-\-old-name" static/src/` is empty. Don't rename all at once.
|
|
|
|
## Anti-patterns seen in Odoo addons
|
|
|
|
| Smell | Fix |
|
|
| --- | --- |
|
|
| `!important` to beat Bootstrap | scope under the page root class |
|
|
| Same colour as `#28a745` in six files | one token in `variables.css` |
|
|
| `responsive.css` overriding twelve components | move each query into its component |
|
|
| `style="margin-top:20px"` in QWeb | utility class or component spacing |
|
|
| `.card .body .row .col span` | give the span a class |
|
|
| `px` for font sizes | `rem`, so browser zoom and user font size work |
|
|
| Colour hardcoded instead of `var(--primary)` | breaks the website theme editor silently |
|
|
| Both `@import` chain **and** manifest entries | double download; pick one (see SKILL.md §6) |
|
|
|
|
## Formatting
|
|
|
|
Prettier handles `.css` via pre-commit: `printWidth: 100`, `tabWidth: 4`. Run
|
|
`pre-commit run --all-files` (or `make format`) before committing; never hand-align properties, it
|
|
will be reformatted.
|