Textualize/rich · error · ValueError

level must be 'head', 'row' or 'foot'

Error message

level must be 'head', 'row' or 'foot'

What it means

Box.row (the method that renders one horizontal rule of a box character set) only accepts level values "head", "row", "mid", or "foot"; anything else raises ValueError("level must be 'head', 'row' or 'foot'"). This is essentially an internal API used by Table rendering — the error message even forgets to mention the accepted "mid" level. End users normally never call it directly.

Source

Thrown at rich/box.py:150

            cross = self.head_row_cross
            right = self.head_row_right
        elif level == "row":
            left = self.row_left
            horizontal = self.row_horizontal
            cross = self.row_cross
            right = self.row_right
        elif level == "mid":
            left = self.mid_left
            horizontal = " "
            cross = self.mid_vertical
            right = self.mid_right
        elif level == "foot":
            left = self.foot_row_left
            horizontal = self.foot_row_horizontal
            cross = self.foot_row_cross
            right = self.foot_row_right
        else:
            raise ValueError("level must be 'head', 'row' or 'foot'")

        parts: List[str] = []
        append = parts.append
        if edge:
            append(left)
        for last, width in loop_last(widths):
            append(horizontal * width)
            if not last:
                append(cross)
        if edge:
            append(right)
        return "".join(parts)

    def get_bottom(self, widths: Iterable[int]) -> str:
        """Get the bottom of a simple box.

        Args:
            widths (List[int]): Widths of columns.

View on GitHub (pinned to 9d8f9a372c)

Solutions

  1. Pass one of "head", "row", "mid", or "foot" as the level
  2. If you don't need a semantic row type, use the default (typically "row")
  3. Prefer composing boxes via the predefined box constants (box.SQUARE, box.DOUBLE, ...) rather than calling Box.row yourself

Example fix

// before (Python)
sep = box.SQUARE.row([], widths, level="header")  # ValueError
// after
sep = box.SQUARE.row([], widths, level="head")
Defensive patterns

Strategy: validation

Validate before calling

if level not in ("head", "row", "mid", "foot"):
    raise ValueError(f"invalid box level {level!r}")
line = box.SQUARE.row(characters, widths, level=level)

Type guard

from typing import Literal, TypeGuard

def is_box_level(v: object) -> TypeGuard[str]:
    return v in ("head", "row", "mid", "foot")

Prevention

When it happens

Trigger: Calling box.SQUARE.row(head_row, widths, level=...) with a misspelled or unknown level string; custom Table/box subclasses or plugins that invoke Box.row directly with their own level names; passing None or an empty string.

Common situations: Almost exclusively seen when writing custom table renderers or third-party extensions that reuse Box.row; occasionally triggered by copying example code that hardcodes an invalid level.

Related errors


AI-assisted analysis of Textualize/rich@9d8f9a372c (2026-08-15). Data as JSON: /api/errors/da741d623d7f922e. Report an issue: GitHub.