# CMS-Friendly `<p>` Convention

**Why this matters:** the client CMS strips inline classes (`class`,
`id`, `style`) from `<p>` elements when editors save body text. If
paragraph styling lives on the `<p>` tag itself, it gets lost the
moment an editor opens the page in the admin UI and saves. The
markup in this bundle is structured to survive that round-trip.

## The rule

**Every `<p>` tag in the bundle is bare** — `<p>content</p>` with no
attributes. Styling lives on the wrapping element instead.

> Verified: `grep -c '<p class=' html/**/*.html` returns 0 across
> every file in this bundle.

There are two patterns for the wrapper:

### Pattern 1 — Wrapper `<div>` carries Tailwind utilities

Used when the styling is purely inheriting typography (font, size,
color, leading, alignment, tracking, capitalize, etc.):

```html
<div class="font-heading font-light text-[16px] lg:text-[20px] leading-[26px] lg:leading-[30px] text-primary text-justify">
  <p>Body copy goes here.</p>
</div>
```

The `<p>` inherits all the typography from the wrapper. When the CMS
strips classes off the `<p>`, nothing breaks — the styling never lived
there.

### Pattern 2 — Wrapper carries a semantic `cms-*` class

Used when the styling involves **non-inheriting** properties — i.e.,
properties that don't cascade from the wrapper to the bare `<p>`
naturally. These include:

- `line-clamp-N` (text truncation)
- `mt-auto` (flex-child bottom-push)
- `inline-flex` / `flex` on the `<p>` itself (when the `<p>` is a flex
  container for its `<span>` children)
- `underline` / text-decoration

For these, `css/input.css` defines a `cms-*` semantic class that
targets `> p` via a descendant selector:

```html
<div class="cms-card-excerpt"><p>Truncated 2-line card excerpt</p></div>
```

```css
/* css/input.css */
.cms-card-excerpt > p {
  @apply font-heading font-light text-[13px] lg:text-[14px] leading-snug text-[#1a1a1a] line-clamp-2;
}
```

## The 6 `cms-*` classes currently defined

Listed in `css/input.css` between the existing rules and the `@theme`
block. Each targets `> p` to apply the non-inheriting styling to the
bare `<p>` child.

| Class | Role | Where it's used |
|---|---|---|
| `.cms-card-excerpt` | 2-line card body, small (13/14px), gray | Blog grid cards |
| `.cms-card-excerpt-3` | 3-line truncation modifier (compose with typography utilities on the wrapper) | Homepage + cataract blog cards, component 13-blogs.html desktop excerpt |
| `.cms-doctor-card-book` | Small bottom-aligned Book CTA (13px, no underline). `mt-auto` is on the wrapper class itself (not via `> p`) so the flex bottom-push works. | Doctor cards on `cataract.html` |
| `.cms-doctor-card-book-responsive` | Responsive Book CTA (17px underlined mobile / 13px no-underline desktop). `mt-auto` is on the wrapper class itself. | Doctor cards on homepage, cataract, specialities, 06-doctor-cards component |
| `.cms-sidebar-list-title` | Sidebar list-item 2-line clamp title | Blog sidebar (Reader's Favourite, Hindi Blogs) |
| `.cms-doctor-meta` | `inline-flex` container for `<span>`-with-pipe-separator meta rows | `doctor-individual.html` experience/location row |

**Important detail** — for `cms-doctor-card-book` and `cms-doctor-card-book-responsive`,
the `mt-auto` (flex bottom-push) is on the **wrapper class itself**,
not on `> p`. The bare `<p>` is no longer the flex child of the card
body — the wrapper `<div>` is — so the auto-margin has to live on the
wrapper to take effect.

## When you add new `<p>` content in Twig templates

1. **Render a bare `<p>`** inside whatever wrapper Twig spits out.
2. **Put any new styling on the wrapper.** If the styling is pure
   inheriting typography, use Tailwind utility classes directly on
   the wrapper.
3. **If you need a non-inheriting property** (line-clamp, mt-auto,
   underline, flex on `<p>` itself):
   - Look in `css/input.css` for an existing `cms-*` class that fits.
   - If none fits, add a new one. Naming: `cms-<role>[-<modifier>]`,
     placed alongside existing classes in the same role family.
   - Always use the `> p` descendant selector (or put truly non-CSS-inheriting
     properties like `mt-auto` on the class itself, depending on which
     element is the flex/grid child — see the `cms-doctor-card-book*`
     classes for the latter pattern).

## What about `<h1>`, `<h2>`, `<h3>`, `<span>`?

The CMS rule **only applies to `<p>`** — the client confirmed they
edit body paragraphs through a rich-text widget, which is what strips
the classes. Heading elements (`<h1>`–`<h6>`) and inline elements
(`<span>`, `<a>`, `<strong>`, `<em>`) keep their classes as-is.

## What about `<table>` cells (`<td>`, `<th>`)?

Same as headings — they keep their classes. The CMS table widget
preserves cell structure. The Surgery Cost table on the cataract page
shows the pattern: cells styled directly, with bare `<p>` inside if
the content is a paragraph.

## What about inline `style="..."` on `<p>`?

Same rule — move it to the wrapper. Search the codebase for
`style="letter-spacing: -0.88px;"` to see how the form-subtitle
`<p>` pattern handles this.

## What about `lang="hi"`, `data-*` (or other content attributes)?

`lang`, `dir`, `data-*` and other **content-semantic** attributes
stay on the bare `<p>` (they're not styling — they're metadata about
the text content for screen readers, font fallback, or JS hooks).
The CMS preserves these. Only styling attributes (`class`, `id`,
`style`) get stripped. Example: `data-journey-desc` on the bare `<p>`
in the homepage journey section — JS `querySelector('[data-journey-desc]')`
still resolves correctly.

## Coverage

Every page and every component in this bundle has been refactored:

- All 11 top-level pages (`html/*.html`)
- All 16 component partials (`html/components/01–16-*.html`)
- All per-page component folders (`html/<page>-components/*.html`)

Total `<p class=` count in the bundle: **0**.
