Tutorial2026-08-205 min

python-pptx paragraph fonts: where size, bold, and color actually live

Fonts live on runs; paragraph.font is the paragraph's default; None means inherited, not unset. The missing documentation page for python-pptx text formatting — the inheritance chain, the tf.text trap, and working code you can run in the browser.

In python-pptx, fonts live on runs, and paragraph.fontis the paragraph's default for its runs. Nearly every "my font isn't applying" question comes down to that one sentence. This page is the reference the library's docs are missing: where font properties actually live, why your setting sometimes doesn't take, and working code for the cases people search for.

The 30-second answer

from pptx.util import Pt
from pptx.dml.color import RGBColor

p = text_frame.paragraphs[0]

# Set the default for every run in this paragraph:
p.font.size = Pt(18)
p.font.bold = True
p.font.name = "Calibri"
p.font.color.rgb = RGBColor(0x11, 0x18, 0x27)

# Or set an individual run (wins over the paragraph default):
run = p.add_run()
run.text = "just this bit"
run.font.italic = True

The inheritance chain (why "None" is an answer, not a bug)

Reading p.font.size and getting Nonedoesn't mean 0pt — it means inherited. Effective formatting resolves in this order, first explicit value wins:

  1. Runrun.font
  2. Paragraph defaultparagraph.font
  3. Placeholder / layout / master — whatever the template defines
  4. Theme defaults — the deck's theme fonts

python-pptx only reports what is explicitly set at that level. It does not compute the effective value for you — if you need to know what the text will actually look like, you have to walk up the chain yourself (or render it; more on that below).

The classic trap: tf.text = ... then wondering where your font went

Assigning text_frame.text (or paragraph.text) replaces the content with a single fresh run carrying no direct formatting. So this sequence silently loses the size:

run = p.add_run()
run.text = "Styled"
run.font.size = Pt(24)   # styled run exists...
p.text = "Replaced"       # ...and is now gone, with its formatting

Set text first, then style — or style at the paragraph level, which survives new runs being added since it's the paragraph's default rather than a property of any one run.

Styling every run in an existing text frame

When you load a template deck and want to restyle whatever text is there, iterate all three levels — and remember each shape only has a text frame if has_text_frame is true:

from pptx.util import Pt

def set_font(text_frame, *, size=None, name=None, bold=None):
    for paragraph in text_frame.paragraphs:
        for run in paragraph.runs:
            if size is not None:
                run.font.size = Pt(size)
            if name is not None:
                run.font.name = name
            if bold is not None:
                run.font.bold = bold

for shape in slide.shapes:
    if shape.has_text_frame:
        set_font(shape.text_frame, size=14, name="Calibri")

Setting the run level (not the paragraph level) is deliberate here: it wins over anything, so the result is deterministic regardless of what the template defined.

Try it — edit and render this in your browser

The fastest way to internalize the run-vs-paragraph split is to see it. This sample runs in SlideForge's sandbox (standard python-pptx inside def build(prs): — the runner supplies the Presentation and saves it). Change a Pt()or a color and hit Run; the render comes back in about a second, which python-pptx itself can't do — it writes XML and has no renderer.

Free · no account · renders in about a second

Where the hand-styling tax ends

Everything above is real and works. It is also the tax python-pptx charges for every slide: fonts per run, coordinates per box, and no way to see the result without opening PowerPoint. If what you're building is business slides from structured data, SlideForge renders a typed intent into a themed, editable .pptx in under a second — typography handled by the theme — and its code moderuns your own python-pptx with PDF and full-resolution image export, the two outputs the library can't produce.

Related: python-pptx limitations we solved · Generate PowerPoint in Python

Try SlideForge free

60 free slides, no card required. Generate your first slide in under a minute.