Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

pygame.font.Font.render() creates a new pygame.Surface containing one line of text. It does not place that text on your window by itself: render the text, obtain a rectangle for positioning, then blit the surface to your destination surface.

The complete render-and-display pattern

A minimal working example follows the sequence documented by Pygame: initialize Pygame, create a display and a font, call render(), position the returned surface with a Rect, blit it, and update the display.

import pygame

pygame.init()
screen = pygame.display.set_mode((640, 360))
font = pygame.font.Font(None, 40)

text_surface = font.render("Hello, Pygame!", True, (255, 255, 255))
text_rect = text_surface.get_rect(center=screen.get_rect().center)

screen.fill((30, 30, 30))
screen.blit(text_surface, text_rect)
pygame.display.flip()

# Keep the window open until it is closed.
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

pygame.quit()

The important line is font.render("Hello, Pygame!", True, (255, 255, 255)). The method creates a new surface with the specified text rendered on it. screen.blit() performs the actual drawing operation.

What each argument means

Argument Meaning Example
text A single-line string to rasterize. Null characters raise an error. "Score: 42"
antialias A Boolean. True smooths character edges; False uses a non-antialiased mode. True
color The foreground text color, normally an RGB tuple. (255, 255, 255)
background Optional background color for the text surface. Omit it for transparency outside the glyphs. (0, 0, 0) or omitted

The return value is always a Surface sized for the rendered text. An empty string produces a surface with zero width and the font’s height. That surface has nothing visible to draw, so check your input when text appears to be missing.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing antialiasing and transparency

Antialiased text

Pass True when you want smoother character edges, especially at ordinary UI sizes or when the text is displayed against a varied background. The resulting surface can use per-pixel alpha.

smooth = font.render("Smooth edges", True, (240, 240, 240))
screen.blit(smooth, (20, 20))

Non-antialiased text

Pass False for a harder, pixel-oriented appearance.

pixel_text = font.render("Pixel style", False, (255, 220, 80))
screen.blit(pixel_text, (20, 60))

With antialiasing disabled, Pygame uses an 8-bit two-color palette. Choose the mode based on the visual style you need rather than assuming one is universally faster or better.

Transparent versus solid backgrounds

If you leave background out, pixels outside the glyphs are transparent, allowing the text to sit over an already-painted scene.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
overlay = font.render("Transparent background", True, (255, 255, 255))
screen.blit(overlay, (20, 100))

Supplying a background color creates a solid text rectangle. When the destination always has that known solid background, Pygame can use colorkey transparency instead of alpha values, which may improve performance.

label = font.render("Solid label", True, (20, 20, 20), (220, 220, 220))
screen.blit(label, (20, 140))

Positioning, anchoring, and centering

render() creates pixels but does not select their location. The returned surface provides get_rect(), a Rect whose attributes can be used as anchors.

Center text in the window

surface = font.render("Centered", True, (255, 255, 255))
rect = surface.get_rect(center=screen.get_rect().center)
screen.blit(surface, rect)

The outer screen.get_rect() supplies the window’s center. Assigning that point to the text rectangle’s center keeps the text centered regardless of its width.

Place text at a corner

surface = font.render("Top left", True, (255, 255, 255))
rect = surface.get_rect(topleft=(16, 16))
screen.blit(surface, rect)

Other useful anchors include topright, midtop, midleft, midright, bottomleft, and bottomright. Anchoring with a rectangle is safer than guessing a text width in pixels.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Center on a known horizontal coordinate

title = font.render("Game title", True, (255, 255, 255))
title_rect = title.get_rect(centerx=screen.get_width() // 2, y=12)
screen.blit(title, title_rect)

This centers the title horizontally while keeping its top edge 12 pixels from the top of the display.

Rendering multiple lines

Font.render() renders one line. A newline character is not laid out as a line break; Pygame renders it as an unknown character instead. Split the message and render each line separately.

message = "First linenSecond linenThird line"
lines = message.splitlines()
y = 20

for line in lines:
    line_surface = font.render(line, True, (255, 255, 255))
    screen.blit(line_surface, (20, y))
    y += font.get_linesize()

font.get_linesize() gives consistent vertical spacing based on the font. You can instead advance by each rendered surface’s height when you want spacing tied to the actual result.

y = 20
for line in message.splitlines():
    line_surface = font.render(line, True, (255, 255, 255))
    screen.blit(line_surface, (20, y))
    y += line_surface.get_height()

Center a block of lines

lines = ["Ready", "Press a key", "to begin"]
line_surfaces = [font.render(line, True, (255, 255, 255)) for line in lines]
line_height = font.get_linesize()
block_height = line_height * len(line_surfaces)
start_y = (screen.get_height() - block_height) // 2

for index, line_surface in enumerate(line_surfaces):
    rect = line_surface.get_rect(centerx=screen.get_width() // 2,
                                 y=start_y + index * line_height)
    screen.blit(line_surface, rect)

This is application-level layout: render() supplies each line image, while your loop determines wrapping, spacing, and alignment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rendering in a game loop without unnecessary work

Rendering allocates a new surface. If a label’s text, font, color, and background do not change, render it once and reuse the surface on every frame.

score_surface = font.render("Score: 0", True, (255, 255, 255))
score_rect = score_surface.get_rect(topright=(620, 16))

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((30, 30, 30))
    screen.blit(score_surface, score_rect)
    pygame.display.flip()

When the text changes, replace the cached surface and recalculate its rectangle because the new string can have a different width.

score = 42
score_surface = font.render(f"Score: {score}", True, (255, 255, 255))
score_rect = score_surface.get_rect(topright=(620, 16))

For a destination with a solid known background, the optional background argument can avoid per-pixel alpha handling. For transparent overlays, omit it and retain the alpha-based result.

Font objects and reusable styles

Create a Font object for each size or style you need, then reuse it. A font object controls the rendered height and line spacing used by render().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
heading_font = pygame.font.Font(None, 48)
body_font = pygame.font.Font(None, 28)

heading = heading_font.render("Settings", True, (255, 255, 255))
body = body_font.render("Volume: 80%", True, (200, 200, 200))

Keep the color and background decisions with the visual component that owns the label. This makes it clear which surfaces are transparent overlays and which are solid labels.

When pygame.freetype is a better fit

API Return or drawing behavior Use it when
pygame.font.Font.render Returns one text Surface; your code blits it. You want the standard Pygame font workflow.
pygame.freetype.Font.render Returns a (Surface, Rect) tuple. You want the bounding rectangle together with the rendered result.
pygame.freetype.Font.render_to Renders directly onto an existing surface. You prefer a direct-to-surface call and freetype features.

The regular pygame.font path is usually simplest: render, get a rectangle, and blit. The freetype alternatives are useful when their return value or direct rendering model matches your layout code better.

Troubleshooting common failures

Nothing appears

  • Confirm that you call screen.blit(text_surface, ...); creating the surface alone does not draw it.
  • Call pygame.display.flip() or another display-update function after drawing.
  • Check that the text color contrasts with the pixels behind it and that your rectangle is inside the window.
  • Make sure the string is not empty. An empty string has zero width.

The window closes immediately

A script that reaches its end exits after one frame. Use an event loop, process pygame.QUIT, and keep drawing until the user closes the window, as in the complete example.

Newlines do not create paragraphs

Do not pass a multiline string directly and expect automatic layout. Use splitlines(), render each line, and advance the y-coordinate with font.get_linesize() or a measured height.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Text looks jagged

Pass True for the antialias argument. If you deliberately use False, the hard pixel edges are expected.

Text has an unwanted rectangle

Remove the fourth argument or pass background=None so the area outside glyphs remains transparent. A supplied background color intentionally creates a solid rectangle.

A call raises an error for valid-looking text

Inspect the string for a null character. The documented API rejects null characters. Also verify that the second argument is Boolean-like and that the color is a valid color value such as an RGB tuple.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is to capture a website rather than draw text inside a Pygame window, ScreenshotNeo returns a screenshot or PDF through one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Only clean shots are billed, while bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. The response identifies the result with X-Page-Verdict and X-Billed headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for the complete option list. A direct cURL request is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The equivalent Python request is:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js can call the same endpoint with the supplied query parameters:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', buffer);

ScreenshotNeo also provides an MCP server for AI agents, including Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools. Its Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.

Practical checklist

  • Initialize Pygame before creating the display and font.
  • Create or reuse a Font object.
  • Call render(text, antialias, color, background=None).
  • Use the returned surface’s get_rect() for reliable positioning.
  • Blit the surface to the destination and update the display.
  • Split multiline content yourself.
  • Cache unchanged labels instead of rendering them every frame.
  • Choose pygame.freetype when its tuple return or render_to method better suits your code.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.