Paragraph Borders
Borders and shading around paragraphs — side borders, between-border groups, and the space attribute.
Paragraph borders (w:pBdr) draw border lines around paragraphs and can group consecutive paragraphs into a single bordered box. The spec reads straightforward, but Word's rendering rules have several gotchas that aren't documented.
Structure
w:pPr (paragraph properties)
└── w:pBdr (paragraph borders)
├── w:top (top border)
├── w:bottom (bottom border)
├── w:left (left border)
├── w:right (right border)
├── w:between (between border — separator within groups)
└── w:bar (vertical bar border)
Each border element has:
├── @w:val Border style (single, double, dashed, dotted, nil, none...)
├── @w:sz Width in 1/8 of a point (sz="12" → 1.5pt)
├── @w:space Distance from text to border, in points
└── @w:color Hex color (e.g., "000000")
Basic Example
<w:p>
<w:pPr>
<w:pBdr>
<w:top w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:left w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:bottom w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:right w:val="single" w:sz="12" w:space="4" w:color="000000"/>
</w:pBdr>
</w:pPr>
<w:r><w:t>A paragraph with borders on all four sides.</w:t></w:r>
</w:p>
Between Border Groups
When consecutive paragraphs have identical border definitions AND include a w:between element, Word groups them into a single bordered box. The between border draws as a horizontal separator between group members.
<w:p>
<w:pPr>
<w:pBdr>
<w:top w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:left w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:bottom w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:right w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:between w:val="single" w:sz="6" w:space="1" w:color="000000"/>
</w:pBdr>
</w:pPr>
<w:r><w:t>First paragraph in the group.</w:t></w:r>
</w:p>
<w:p>
<w:pPr>
<w:pBdr>
<w:top w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:left w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:bottom w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:right w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:between w:val="single" w:sz="6" w:space="1" w:color="000000"/>
</w:pBdr>
</w:pPr>
<w:r><w:t>Second paragraph — between border separates them.</w:t></w:r>
</w:p>
Nil/None Between — Grouping Without a Separator
Setting w:between to val="nil" or val="none" does NOT mean "don't group." It means "group these paragraphs but don't draw a separator." The result is a single continuous bordered box with no divider between paragraphs.
<w:p>
<w:pPr>
<w:pBdr>
<w:top w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:left w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:bottom w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:right w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:between w:val="nil"/>
</w:pBdr>
</w:pPr>
<w:r><w:t>First paragraph — no between separator.</w:t></w:r>
</w:p>
<w:p>
<w:pPr>
<w:pBdr>
<w:top w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:left w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:bottom w:val="single" w:sz="12" w:space="1" w:color="000000"/>
<w:right w:val="single" w:sz="12" w:space="4" w:color="000000"/>
<w:between w:val="nil"/>
</w:pBdr>
</w:pPr>
<w:r><w:t>Second paragraph — continuous box, no divider.</w:t></w:r>
</w:p>
How Word Renders Groups
The spec describes w:between but doesn't spell out the rendering rules. Here's what Word actually does with a group of 3 paragraphs:
┌─────────────────────────────┐ ← top border (from A)
│ Paragraph A text │
├─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┤ ← between border
│ Paragraph B text │
├─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─┤ ← between border
│ Paragraph C text │
└─────────────────────────────┘ ← bottom border (from C)
- A: top + left + right + between-as-bottom
- B: left + right + between-as-bottom (top suppressed)
- C: left + right + bottom (top suppressed)
- Left/right borders bridge paragraph spacing gaps
Implementation Notes
Consecutive paragraphs form a group when they all have a w:between element AND all border properties match (top, bottom, left, right, between). Crucially, val="nil" or val="none" still triggers grouping — it means "group without a separator," not "don't group." If you normalize nil/none to undefined during parsing, you lose the grouping signal entirely.
The space attribute sets the distance (in points) between a border's inner edge and the text. For between borders, this padding applies on both sides — above and below. Note that sz uses a different unit: eighths of a point (sz="12" = 1.5pt). Easy to mix up since they're on the same element.
Schema
| Element | Description |
|---|---|
w:top | Top border |
w:bottom | Bottom border |
w:left | Left border |
w:right | Right border |
w:between | Border between grouped paragraphs |
w:bar | Vertical bar border (drawn outside the paragraph) |
| Attribute | Type | Description |
|---|---|---|
w:val | ST_Border | Border style — single, double, dashed, dotted, nil, none, etc. |
w:sz | integer | Width in 1/8 of a point (e.g., 12 = 1.5pt) |
w:space | integer | Distance from text to border inner edge, in points |
w:color | hex | Border color (e.g., 000000, auto) |
w:shadow | boolean | Shadow effect on the border |
w:frame | boolean | Frame effect on the border |
Spec reference: ECMA-376 §17.3.1.24 (pBdr), §17.3.1.7 (bottom border), §17.3.1.31 (shd)