HTML & CSS Style Guide

Per-language style guide for HTML and CSS. Shared rules: the Coding Style Guide. Requirement levels follow RFC 2119; tags ๐ŸŒŽ / ๐Ÿ  are defined there.

1. Formatting ๐ŸŒŽ

Note: Prettier is the single formatter across TypeScript, Markdown, HTML, and CSS. Stylelint MAY be added for lint rules Prettier does not cover (e.g. declaration order, disallowed units), but MUST NOT re-implement formatting.

Note: Prettier is mandated because its HTML formatter is the only stable one. Biome (CSS stable, HTML still experimental) and oxfmt (beta, ~30ร— faster) are Prettier-compatible alternatives to revisit once their HTML support stabilizes. A toolchain-wide switch MUST be an ADR, not a per-page choice.

2. Semantic HTML ๐ŸŒŽ

โŒ A styled <div> reimplements a button โ€” no keyboard focus, no Enter/Space, invisible to assistive tech:

<div class="btn" onclick="save()">Save</div>

โœ… The native element carries behavior and semantics for free:

<button type="button" class="btn" onclick="save()">Save</button>

3. Accessibility ๐Ÿ 

โŒ An icon-only control gives assistive tech nothing to announce:

<button type="button">
  <svg><!-- trash icon --></svg>
</button>

โœ… An accessible name is provided:

<button type="button" aria-label="Delete item">
  <svg aria-hidden="true"><!-- trash icon --></svg>
</button>

Rationale: stricter than mainstream, which treats accessibility as optional polish. OSBR treats WCAG 2.2 AA as a baseline requirement, not an enhancement.

4. Document Structure ๐ŸŒŽ

5. Styling Separation ๐Ÿ 

โŒ Presentation is hard-wired into markup and script:

<p style="color: red; margin-top: 20px">Payment failed</p>

โœ… State is a class; appearance is CSS:

<p class="alert alert--error">Payment failed</p>

Rationale: divergent from framework conventions (utility-class and inline-style libraries) where style-in-markup is idiomatic. Separation keeps the cascade as the single source of presentation. Utility-first frameworks (ยง9) are the sanctioned exception.

6. Selectors & Specificity ๐Ÿ 

โŒ High specificity and nesting that nothing can override cleanly:

#sidebar ul li a.active {
  color: blue !important;
}

โœ… A single flat class, specificity 0-1-0:

.nav-link--active {
  color: blue;
}

Rationale: a strong default, not an absolute. Flat, class-only selectors keep specificity predictable and the cascade debuggable; deviate only with reason.

7. Naming ๐Ÿ 

โŒ Appearance-based names break the moment the design changes:

<div class="box red-border big-text">โ€ฆ</div>

โœ… Names describe role and state:

<div class="alert alert--error alert--prominent">โ€ฆ</div>

Rationale: BEM is one convention among several (also SMACSS, utility-first). OSBR recommends BEM as the default so class names read the same across repositories; a repo MAY choose another convention and apply it consistently.

8. Layout, Units & Properties ๐ŸŒŽ

โŒ Fixed pixel type ignores the user's font-size preference:

.card__title {
  font-size: 18px;
}

โœ… Type scales with the root font size:

.card__title {
  font-size: 1.125rem;
}

9. Custom Properties & Frameworks ๐Ÿ 

โŒ The same brand color is copied into every rule that needs it:

.btn { background: #0055ff; }
.link { color: #0055ff; }

โœ… One token, referenced everywhere:

:root { --color-brand: #0055ff; }

.btn { background: var(--color-brand); }
.link { color: var(--color-brand); }

Rationale: stricter than the platform, which imposes no token discipline. Centralized tokens make theming and design-system changes a one-line edit.

10. Performance ๐ŸŒŽ

โŒ No dimensions and a render-blocking script โ€” the layout jumps and paint stalls:

<head>
  <script src="/app.js"></script>
</head>
<body>
  <img src="/hero.jpg" alt="Product" />
</body>

โœ… Space is reserved and the script defers:

<head>
  <script src="/app.js" defer></script>
</head>
<body>
  <img src="/hero.jpg" alt="Product" width="1200" height="600" />
</body>

11. Security ๐ŸŒŽ

โŒ The new tab can hijack the opener, and a token leaks in a comment:

<!-- TODO: rotate before launch โ€” api key EXAMPLE-API-KEY-DO-NOT-USE -->
<a href="https://partner.example" target="_blank">Partner portal</a>

โœ… The link is isolated and no secret is shipped:

<a href="https://partner.example" target="_blank" rel="noopener noreferrer">
  Partner portal
</a>

12. Internationalization ๐ŸŒŽ

Many OSBR projects ship in more than one language (typically English and Japanese). Markup and CSS MUST support this from the start.

โŒ A Latin-only font, a fixed-height box, and mid-character breaking mangle Japanese:

<p lang="ja" class="notice">ใŠๆ”ฏๆ‰•ใ„ใŒๅฎŒไบ†ใ—ใพใ—ใŸใ€‚</p>
.notice {
  height: 20px;              /* clips when the translation is taller */
  font-family: Arial, sans-serif;   /* no Japanese glyphs */
  word-break: break-all;     /* breaks mid-word */
}

โœ… Content-sized box, CJK fallback, and script-aware breaking:

.notice {
  font-family: system-ui, "Noto Sans JP", "Hiragino Sans", "Yu Gothic",
    sans-serif;
  line-break: strict;
}

References