Textualize/textual · error · ValueError
Template must contain at least one non-separator character
Error message
Template must contain at least one non-separator character
What it means
A ValueError raised in MaskedInput.__init__ (via _Template construction) when every character slot in the template is a separator — i.e. there is no position for user input. A mask like '--' or '//' with only separator characters cannot hold a value, so construction fails.
Source
Thrown at src/textual/widgets/_masked_input.py:141
char_definition = self.CharDefinition(
re.compile(pattern), char_flags
)
else:
char_definition = self.CharDefinition(
re.compile(re.escape(char)), _CharFlags.SEPARATOR, char
)
char_definition.flags |= flags
self.template.append(char_definition)
if template_chars:
self.blank = template_chars[0]
if all(
(_CharFlags.SEPARATOR in char_definition.flags)
for char_definition in self.template
):
raise ValueError(
"Template must contain at least one non-separator character"
)
self.update_mask(input.placeholder)
def validate(self, value: str) -> ValidationResult:
"""Checks if `value` matches this template, always returning a ValidationResult.
Args:
value: The string value to be validated.
Returns:
A ValidationResult with the validation outcome.
"""
if self.check(value.ljust(len(self.template), chr(0)), False):
return self.success()
else:View on GitHub (pinned to 06dbeef4bb)
Solutions
- Ensure the template contains at least one input character (e.g. '9999-9999', not '-----').
- Validate user-supplied templates before constructing MaskedInput (check for at least one non-separator slot).
- Provide a sensible default template when validation fails.
- Consult MaskedInput docs for valid template characters (1, a, A, 9, *, etc.).
Example fix
# before field = MaskedInput(template=cfg['mask']) # cfg['mask'] == '--' # after mask = cfg['mask'] if any(c in '1aA9*@#' for c in cfg['mask']) else '999-9999' field = MaskedInput(template=mask)
Defensive patterns
Strategy: validation
Validate before calling
INPUT_CHARS = set('1aA9*@#')
assert any(c in INPUT_CHARS for c in template), 'mask has no input slots'
MaskedInput(template=template) Try / catch
try:
field = MaskedInput(template=mask)
except ValueError:
field = MaskedInput(template='999-9999') # fallback mask Prevention
- Validate config-supplied masks contain at least one input slot
- Default to a known-good mask on invalid input
- Refer to MaskedInput docs for valid template characters
When it happens
Trigger: Passing a template consisting solely of separator characters (e.g. MaskedInput(template='///')); a template built dynamically from config where all input slots were dropped; templates like '()' where every char is a literal separator.
Common situations: Config-driven masks for phone/date formats where a malformed pattern slips through; building templates by string concatenation that accidentally removes all input slots; typos using a wrong mask character for every slot.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Value does not match template!
- More values provided than there are columns.
- Input type must be one of {friendly_list(_RESTRICT_TYPES.key
- Can't animate attribute {attribute!r} on {obj!r}; attribute
- Don't know how to animate {value!r}; Can only animate <int>,
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/576f3fa86fba9a95.
Report an issue: GitHub.