Element Hierarchy Rules
Valid Document Structure
OOXML has strict rules about which elements can contain which other elements. Understanding these rules prevents document corruption and makes debugging easier.
The Complete Hierarchy
Rule Categories
1. Block-Level Elements
Can appear directly in: w:body, table cells (w:tc), SDT content, headers/footers
Elements:
w:p- Paragraphw:tbl- Tablew:sdt- Content control (block variant)
<!-- ✅ VALID: Block elements in body -->
<w:body>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
<w:sdt>...</w:sdt>
<w:p>...</w:p>
</w:body>
❌ Cannot be in: Runs, inline elements
2. Inline Elements
Must appear inside: Paragraphs or inline containers
Elements:
w:r- Runw:hyperlink- Hyperlinkw:sdt- Content control (inline variant)
<!-- ✅ VALID: Inline elements in paragraph -->
<w:p>
<w:r>...</w:r>
<w:hyperlink>...</w:hyperlink>
<w:sdt>...</w:sdt>
</w:p>
❌ Cannot be in: Body (must be wrapped in paragraph)
3. Content Elements
Must appear inside: Runs
Elements:
w:t- Textw:br- Line breakw:tab- Tabw:drawing- Imagew:sym- Symbol
<!-- ✅ VALID: Content in run -->
<w:r>
<w:t>Text</w:t>
<w:br/>
<w:tab/>
</w:r>
❌ Cannot be in: Paragraphs directly, body, table cells directly
Detailed Rules by Element
Document Root
<w:document> <!-- Root element -->
<w:body> <!-- Exactly one body -->
<!-- Block elements -->
</w:body>
</w:document>
Rules:
- Must have exactly one
w:bodychild - May have
w:background(optional) - Namespace declarations required
Body (w:body)
Valid children (in order):
- Any number of block elements (
w:p,w:tbl,w:sdt) - Exactly one
w:sectPr(section properties) at the end
<w:body>
<w:p>...</w:p> <!-- Paragraphs -->
<w:tbl>...</w:tbl> <!-- Tables -->
<w:sdt>...</w:sdt> <!-- Block SDTs -->
<w:p>...</w:p> <!-- More content -->
<w:sectPr> <!-- Section properties (last) -->
<w:pgSz w:w="12240" w:h="15840"/> <!-- Page size -->
<w:pgMar w:top="1440" w:bottom="1440"/> <!-- Margins -->
</w:sectPr>
</w:body>
Rules:
- Can contain unlimited block elements
w:sectPrmust be last (if present)- Cannot directly contain runs or text
Paragraph (w:p)
Valid children (in order):
w:pPr(paragraph properties) - optional, must be first- Any number of inline elements
<w:p>
<w:pPr> <!-- Properties first -->
<w:pStyle w:val="Normal"/>
<w:jc w:val="center"/>
</w:pPr>
<w:r>...</w:r> <!-- Runs -->
<w:hyperlink>...</w:hyperlink> <!-- Hyperlinks -->
<w:sdt>...</w:sdt> <!-- Inline SDTs -->
<w:bookmarkStart/> <!-- Bookmarks -->
<w:bookmarkEnd/>
</w:p>
Rules:
w:pPrmust be first child (if present)- Can be empty:
<w:p/> - Cannot contain tables or block SDTs
- Cannot contain text directly (must use runs)
Common mistake:
<!-- ❌ WRONG: Text without run -->
<w:p>
<w:t>Text</w:t>
</w:p>
<!-- ✅ CORRECT -->
<w:p>
<w:r>
<w:t>Text</w:t>
</w:r>
</w:p>
Run (w:r)
Valid children (in order):
w:rPr(run properties) - optional, must be first- Any number of content elements
<w:r>
<w:rPr> <!-- Properties first -->
<w:b/>
<w:sz w:val="24"/>
</w:rPr>
<w:t>Text</w:t> <!-- Text -->
<w:br/> <!-- Line break -->
<w:tab/> <!-- Tab -->
<w:drawing>...</w:drawing> <!-- Image -->
<w:sym w:font="Wingdings" w:char="F020"/> <!-- Symbol -->
</w:r>
Rules:
w:rPrmust be first child (if present)- Cannot contain paragraphs, tables, or block elements
- Cannot contain other runs
- Cannot be empty (should have at least one content element)
Common mistakes:
<!-- ❌ WRONG: Paragraph in run -->
<w:r>
<w:p>...</w:p>
</w:r>
<!-- ❌ WRONG: Table in run -->
<w:r>
<w:tbl>...</w:tbl>
</w:r>
<!-- ❌ WRONG: SDT in run (block-level SDT) -->
<w:r>
<w:sdt>
<w:sdtContent><w:p>...</w:p></w:sdtContent>
</w:sdt>
</w:r>
Table (w:tbl)
Valid children (in order):
w:tblPr(table properties) - optional, must be firstw:tblGrid(grid definition) - required- One or more
w:tr(table rows)
<w:tbl>
<w:tblPr> <!-- Properties first -->
<w:tblW w:w="5000" w:type="pct"/>
<w:tblBorders>...</w:tblBorders>
</w:tblPr>
<w:tblGrid> <!-- Grid definition -->
<w:gridCol w:w="2500"/>
<w:gridCol w:w="2500"/>
</w:tblGrid>
<w:tr>...</w:tr> <!-- Rows -->
<w:tr>...</w:tr>
</w:tbl>
Rules:
- Must have at least one row (
w:tr) w:tblGridis required- Cannot be directly in runs or inline elements
- Can be nested inside table cells
Valid locations:
<!-- ✅ In body -->
<w:body>
<w:tbl>...</w:tbl>
</w:body>
<!-- ✅ In table cell (nested table) -->
<w:tc>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
</w:tc>
<!-- ✅ In SDT content -->
<w:sdt>
<w:sdtContent>
<w:tbl>...</w:tbl>
</w:sdtContent>
</w:sdt>
<!-- ❌ WRONG: In run -->
<w:r>
<w:tbl>...</w:tbl>
</w:r>
Table Row (w:tr)
Valid children (in order):
w:trPr(row properties) - optional, must be first- One or more
w:tc(table cells)
<w:tr>
<w:trPr> <!-- Properties first -->
<w:trHeight w:val="500" w:hRule="atLeast"/>
<w:cantSplit/>
</w:trPr>
<w:tc>...</w:tc> <!-- Cells -->
<w:tc>...</w:tc>
</w:tr>
Rules:
- Must have at least one cell
- Cannot exist outside of table
- All cells in a row should align with table grid
Table Cell (w:tc)
Valid children (in order):
w:tcPr(cell properties) - optional, must be first- At least one
w:p(paragraph) - REQUIRED - Optional additional paragraphs or nested tables
<w:tc>
<w:tcPr> <!-- Properties first -->
<w:tcW w:w="2500" w:type="dxa"/>
<w:vMerge w:val="restart"/>
</w:tcPr>
<w:p> <!-- At least one paragraph REQUIRED -->
<w:r><w:t>Cell content</w:t></w:r>
</w:p>
<w:p>...</w:p> <!-- More paragraphs OK -->
<w:tbl>...</w:tbl> <!-- Nested table OK -->
</w:tc>
Critical rule: ⚠️ Every table cell MUST contain at least one paragraph!
<!-- ❌ WRONG: Cell without paragraph -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:r><w:t>Text</w:t></w:r> <!-- No paragraph wrapper! -->
</w:tc>
<!-- ✅ CORRECT: Cell with paragraph -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:p>
<w:r><w:t>Text</w:t></w:r>
</w:p>
</w:tc>
<!-- ✅ CORRECT: Empty cell (still has paragraph) -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:p/> <!-- Empty paragraph -->
</w:tc>
This is one of the most common mistakes in document generation!
Content Control (w:sdt)
Content controls can be block-level or inline, with different rules for each.
Block-Level SDT
Valid children:
w:sdtPr(properties) - required, must be firstw:sdtEndPr(end properties) - optionalw:sdtContent- required, must be last
Can contain: Block elements (paragraphs, tables)
<!-- ✅ Block SDT in body -->
<w:body>
<w:sdt>
<w:sdtPr>
<w:id w:val="123"/>
<w:tag w:val="fieldName"/>
</w:sdtPr>
<w:sdtContent>
<w:p>...</w:p> <!-- Paragraphs -->
<w:tbl>...</w:tbl> <!-- Tables -->
</w:sdtContent>
</w:sdt>
</w:body>
Inline SDT
Valid children:
w:sdtPr(properties) - required, must be firstw:sdtEndPr(end properties) - optionalw:sdtContent- required, must be last
Can contain: Inline elements (runs)
<!-- ✅ Inline SDT in paragraph -->
<w:p>
<w:sdt>
<w:sdtPr>
<w:id w:val="456"/>
<w:tag w:val="inlineField"/>
</w:sdtPr>
<w:sdtContent>
<w:r>...</w:r> <!-- Runs only -->
</w:sdtContent>
</w:sdt>
</w:p>
Common mistake:
<!-- ❌ WRONG: SDT inside run -->
<w:p>
<w:r>
<w:sdt>...</w:sdt>
</w:r>
</w:p>
<!-- ✅ CORRECT: SDT inside paragraph, contains runs -->
<w:p>
<w:sdt>
<w:sdtContent>
<w:r>...</w:r>
</w:sdtContent>
</w:sdt>
</w:p>
🔗 See Also: Content Controls (SDT)
Properties Order Rules
Paragraph Properties Order
When multiple properties exist, they must follow this order:
<w:pPr>
<!-- Style reference first -->
<w:pStyle w:val="Normal"/>
<!-- Keep options -->
<w:keepNext/>
<w:keepLines/>
<!-- Numbering -->
<w:numPr>...</w:numPr>
<!-- Spacing -->
<w:spacing w:before="120" w:after="120"/>
<!-- Indentation -->
<w:ind w:left="720"/>
<!-- Alignment -->
<w:jc w:val="center"/>
<!-- Borders and shading -->
<w:pBdr>...</w:pBdr>
<w:shd>...</w:shd>
</w:pPr>
Rule: Order matters! Wrong order can cause validation errors.
Run Properties Order
<w:rPr>
<!-- Style reference first -->
<w:rStyle w:val="Emphasis"/>
<!-- Font -->
<w:rFonts w:ascii="Times New Roman"/>
<!-- Size -->
<w:sz w:val="24"/>
<w:szCs w:val="24"/>
<!-- Color -->
<w:color w:val="FF0000"/>
<!-- Formatting flags -->
<w:b/>
<w:i/>
<w:u w:val="single"/>
<!-- Highlight -->
<w:highlight w:val="yellow"/>
</w:rPr>
Complete Valid Structures
Simple Document
<w:document xmlns:w="...">
<w:body>
<w:p>
<w:r>
<w:t>Hello World</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
Hierarchy: document → body → p → r → t
Document with Formatted Text
<w:body>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>Bold 14pt Text</w:t>
</w:r>
</w:p>
</w:body>
Document with Table
<w:body>
<w:tbl>
<w:tblPr>
<w:tblW w:w="5000" w:type="pct"/>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="2500"/>
<w:gridCol w:w="2500"/>
</w:tblGrid>
<w:tr>
<w:tc>
<w:p>
<w:r><w:t>Cell 1</w:t></w:r>
</w:p>
</w:tc>
<w:tc>
<w:p>
<w:r><w:t>Cell 2</w:t></w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
</w:body>
Document with Image
<w:body>
<w:p>
<w:r>
<w:drawing>
<wp:inline xmlns:wp="...">
<wp:extent cx="914400" cy="914400"/>
<wp:docPr id="1" name="Image"/>
<a:graphic xmlns:a="...">
<a:graphicData uri="...">
<pic:pic xmlns:pic="...">
<pic:blipFill>
<a:blip r:embed="rId5"/>
</pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
</w:body>
Hierarchy: body → p → r → drawing → inline → graphic → pic
Quick Reference Matrix
Can X contain Y?
| Parent ↓ / Child → | p | tbl | sdt | r | t | drawing |
|---|---|---|---|---|---|---|
| body | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| p | ❌ | ❌ | ✅* | ✅ | ❌ | ❌ |
| r | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| tc | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| sdt(block) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| sdt(inline) | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
*Only inline SDT in paragraph
Validation Rules Summary
✅ Always Valid
body → p → r → tbody → tbl → tr → tc → pbody → sdt → p → r → ttc → tbl(nested table)p → hyperlink → r → t
❌ Never Valid
r → p(run cannot contain paragraph)r → tbl(run cannot contain table)r → sdt(block)(run cannot contain block SDT)p → tbl(paragraph cannot contain table)p → t(paragraph cannot directly contain text)tcwithoutp(cell must have paragraph)
AppraiSys Patterns
Creating Valid Structure
// ✅ Correct hierarchy
var body = mainPart.Document.Body;
// Add paragraph
var paragraph = new Paragraph();
// Add run to paragraph
var run = new Run();
paragraph.Append(run);
// Add text to run
var text = new Text("Content");
run.Append(text);
// Add paragraph to body
body.Append(paragraph);
// Final structure: body → p → r → t ✅
Table Cell with Required Paragraph
// ✅ Correct: Cell always has paragraph
var cell = new TableCell();
// Properties
cell.Append(new TableCellProperties(
new TableCellWidth { Width = "2500", Type = TableWidthUnitValues.Dxa }
));
// Required paragraph
cell.Append(new Paragraph(
new Run(
new Text("Cell content")
)
));
row.Append(cell);
Common Validation Errors
Error: "Unexpected element"
Element 'http://...wordprocessingml/2006/main:tbl'
cannot be a child of element 'http://...wordprocessingml/2006/main:r'
Cause: Table inside run (violates hierarchy rules)
Fix: Move table to body level
Error: "Required element missing"
Element 'http://...wordprocessingml/2006/main:tc'
is incomplete. Expected element 'http://...wordprocessingml/2006/main:p'
Cause: Table cell without paragraph
Fix: Add at least one paragraph to cell
Error: "Element order incorrect"
Element 'http://...wordprocessingml/2006/main:r'
cannot appear after element 'http://...wordprocessingml/2006/main:pPr'
Cause: Properties must come before content
Fix: Reorder elements (properties first)
Next Steps
You now understand valid element hierarchies. Complete your fundamentals knowledge with practice:
➡️ Next: Practice Exercises - Hands-on learning
Review:
- Common Fatal Mistakes - What NOT to do
- WordprocessingML Core - Element basics
Advanced:
- Content Controls - SDT deep dive
- Tables - Table complete guide
Test Your Understanding
Question 1
Is this structure valid?
<w:body>
<w:tbl>
<w:tr>
<w:tc>
<w:r><w:t>Text</w:t></w:r>
</w:tc>
</w:tr>
</w:tbl>
</w:body>
Answer
No! The table cell is missing a required paragraph:
<w:tc>
<w:p> <!-- Required paragraph -->
<w:r><w:t>Text</w:t></w:r>
</w:p>
</w:tc>
Every table cell must contain at least one paragraph.
Question 2
Can a paragraph contain a table?
Answer
No! Tables are block-level elements. They cannot be inside paragraphs.
Tables must be at the body level (or inside table cells, SDT content, etc.):
<!-- ❌ WRONG -->
<w:p>
<w:tbl>...</w:tbl>
</w:p>
<!-- ✅ CORRECT -->
<w:body>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
</w:body>
Question 3
What's the minimum valid content for a table cell?
Answer
An empty paragraph:
<w:tc>
<w:p/>
</w:tc>
Or with properties:
<w:tc>
<w:tcPr>
<w:tcW w:w="2500" w:type="dxa"/>
</w:tcPr>
<w:p/>
</w:tc>
The cell MUST have at least one paragraph, even if empty.
💡 Remember: OOXML hierarchy rules are strict. Follow them, and your documents will be valid every time!