Last modified: Jul 08, 2026

Python-docx Table Borders & Shading Guide

Tables in Word documents bring structure to data. But plain tables look dull. With python-docx, you can add borders and shading to make tables pop. This guide shows you how to control every line and color.

We will cover table borders, cell borders, and cell shading. Each section includes clean code and output. You will learn to create professional tables in minutes.

Setting Up Your Environment

First, install python-docx. Open your terminal and run:


pip install python-docx

Import the library in your script. Create a document and add a table.


from docx import Document
from docx.shared import Inches, Pt, RGBColor
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn

doc = Document()
table = doc.add_table(rows=3, cols=3)
table.alignment = WD_TABLE_ALIGNMENT.CENTER

Understanding Table Borders in python-docx

Borders are controlled through XML. python-docx provides helper methods. Use table.style to apply a predefined style. For custom borders, modify the underlying XML.

Each table has a tblPr element. Inside it, tblBorders defines border properties. You can set top, bottom, left, right, insideH, and insideV borders.

Here is how to add a simple border to all cells.


from docx.oxml import OxmlElement

def set_table_borders(table):
    tbl = table._tbl
    tblPr = tbl.tblPr if tbl.tblPr is not None else OxmlElement('w:tblPr')
    borders = OxmlElement('w:tblBorders')
    
    for border_name in ['top', 'left', 'bottom', 'right', 'insideH', 'insideV']:
        border = OxmlElement(f'w:{border_name}')
        border.set(qn('w:val'), 'single')
        border.set(qn('w:sz'), '4')
        border.set(qn('w:space'), '0')
        border.set(qn('w:color'), '000000')
        borders.append(border)
    
    tblPr.append(borders)

set_table_borders(table)
doc.save('bordered_table.docx')

Explanation: The function creates border elements. Each border has a style (val), size (sz), spacing, and color. The size is in eighths of a point. So sz='4' means 0.5 points.

Customizing Border Colors and Widths

You can change the color and width easily. Use hex color codes. For example, FF0000 for red. Adjust sz for thicker or thinner lines.


def set_colored_borders(table):
    tbl = table._tbl
    tblPr = tbl.tblPr if tbl.tblPr is not None else OxmlElement('w:tblPr')
    borders = OxmlElement('w:tblBorders')
    
    # Top border: thick red
    top = OxmlElement('w:top')
    top.set(qn('w:val'), 'single')
    top.set(qn('w:sz'), '12')  # 1.5 points
    top.set(qn('w:color'), 'FF0000')
    borders.append(top)
    
    # Other borders: thin blue
    for border_name in ['left', 'bottom', 'right', 'insideH', 'insideV']:
        border = OxmlElement(f'w:{border_name}')
        border.set(qn('w:val'), 'single')
        border.set(qn('w:sz'), '4')
        border.set(qn('w:color'), '0000FF')
        borders.append(border)
    
    tblPr.append(borders)

set_colored_borders(table)

This creates a table with a thick red top border and thin blue borders elsewhere. Useful for header emphasis.

Cell-Level Borders

Sometimes you need borders on specific cells. Use the tcPr element of each cell. This gives fine control.

Here is how to add a bottom border to the first row only.


from docx.oxml import OxmlElement
from docx.oxml.ns import qn

def set_cell_border(cell, **kwargs):
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()
    tcBorders = OxmlElement('w:tcBorders')
    for edge, attrs in kwargs.items():
        element = OxmlElement(f'w:{edge}')
        for attr, val in attrs.items():
            element.set(qn(f'w:{attr}'), val)
        tcBorders.append(element)
    tcPr.append(tcBorders)

# Add bottom border to first row cells
for cell in table.rows[0].cells:
    set_cell_border(cell, bottom={'val': 'single', 'sz': '6', 'color': 'FF0000'})

Note: The set_cell_border function accepts edge names like top, bottom, left, right. This is powerful for highlighting specific rows or columns.

Removing All Borders

To create a borderless table, set all border values to none.


def remove_table_borders(table):
    tbl = table._tbl
    tblPr = tbl.tblPr if tbl.tblPr is not None else OxmlElement('w:tblPr')
    borders = OxmlElement('w:tblBorders')
    
    for border_name in ['top', 'left', 'bottom', 'right', 'insideH', 'insideV']:
        border = OxmlElement(f'w:{border_name}')
        border.set(qn('w:val'), 'none')
        borders.append(border)
    
    tblPr.append(borders)

remove_table_borders(table)

This is useful for layout tables where only spacing matters.

Cell Shading in python-docx

Shading adds background color to cells. Use cell.shading to set it. The shading object requires a color in hex format.


from docx.oxml import OxmlElement
from docx.oxml.ns import qn

def set_cell_shading(cell, color_hex):
    shading_elm = OxmlElement('w:shd')
    shading_elm.set(qn('w:fill'), color_hex)
    shading_elm.set(qn('w:val'), 'clear')
    cell._tc.get_or_add_tcPr().append(shading_elm)

# Shade header row light blue
header_color = 'D9E2F3'
for cell in table.rows[0].cells:
    set_cell_shading(cell, header_color)

# Shade alternating rows
for i, row in enumerate(table.rows[1:], start=1):
    if i % 2 == 0:
        for cell in row.cells:
            set_cell_shading(cell, 'F2F2F2')

Explanation: The shd element uses fill for background color. val='clear' means solid fill. You can also use patterns with val like pct10.

Combining Borders and Shading

Combine both techniques for a polished table. Here is a complete example.


from docx import Document
from docx.shared import Inches
from docx.oxml import OxmlElement
from docx.oxml.ns import qn

doc = Document()
table = doc.add_table(rows=4, cols=3)

# Set data
headers = ['Name', 'Age', 'City']
data = [
    ['Alice', '30', 'New York'],
    ['Bob', '25', 'London'],
    ['Charlie', '35', 'Paris']
]

for i, header in enumerate(headers):
    table.rows[0].cells[i].text = header
for row_idx, row_data in enumerate(data, start=1):
    for col_idx, value in enumerate(row_data):
        table.rows[row_idx].cells[col_idx].text = value

# Apply borders
set_table_borders(table)

# Shade header
header_color = '4472C4'
for cell in table.rows[0].cells:
    set_cell_shading(cell, header_color)
    # Make header text white
    for paragraph in cell.paragraphs:
        for run in paragraph.runs:
            run.font.color.rgb = RGBColor(255, 255, 255)

# Shade alternating rows
for i, row in enumerate(table.rows[1:], start=1):
    if i % 2 == 0:
        for cell in row.cells:
            set_cell_shading(cell, 'D6E4F0')

doc.save('styled_table.docx')

This creates a professional table with a dark blue header and alternating row shading. The borders are black and thin.

Working with Table Styles

python-docx includes built-in table styles. Use table.style = 'Light Grid Accent 1' for quick styling. Styles handle borders and shading automatically.


table.style = 'Light Grid Accent 1'

Common styles include Light Shading, Medium Grid 1, and Colorful List. Check Word's style gallery for more. Styles are great for rapid prototyping.

However, custom XML gives you full control. For production documents, combine styles with manual tweaks. Read our Python docx Best Practices for Clean Generation for tips on maintaining clean code.

Handling Cell Margins and Padding

Borders and shading work with cell margins. Use cell.margin to set spacing inside cells. This prevents text from touching borders.


from docx.shared import Emu

for row in table.rows:
    for cell in row.cells:
        cell.margin.left = Emu(144000)  # 0.1 inch
        cell.margin.right = Emu(144000)
        cell.margin.top = Emu(72000)    # 0.05 inch
        cell.margin.bottom = Emu(72000)

Margins are in EMUs (English Metric Units). 1 inch = 914400 EMU. Adjust as needed.

Common Pitfalls and Solutions

Borders not showing: Ensure you append borders to the correct element. Check that tblPr exists before appending.

Shading not applying: Verify the hex color is valid. Use uppercase letters. For example, FFFFFF not ffffff.

Cell content overflow: Set column widths explicitly. Use cell.width = Inches(1.5).

For complex layouts, consider Python docx Cell Merging: Advanced Table Layouts to merge cells and create headers.

Performance Tips for Large Tables

When working with hundreds of rows, apply styles in bulk. Avoid looping over every cell if not needed. Use table-level borders instead of cell-level.

Batch apply shading using conditional logic. For example, shade every 10th row for readability. Use list comprehensions for speed.

For automation pipelines, see Future of Python DOCX Automation Trends to stay ahead.

Conclusion

Python-docx gives you full control over table borders and cell shading. Use table-level borders for consistency. Use cell-level borders for highlights. Combine shading with borders for professional reports.

Start with the examples above. Experiment with colors and widths. Your Word documents will look polished and clean. Happy coding!