Stixis (stixis) is a CSS serializer and validator for Python 3.14+ with zero runtime dependencies. It generates its grammar tables and TypedDict definitions from W3C @webref/css (ed/css/*.json), derives property browser-support counts from @mdn/browser-compat-data (css/properties/*.json), and builds its test suite from Web Platform Tests (WPT) and Hypothesis.
- Serializes and canonicalizes CSS values against W3C Value Definition Syntax (
serialize_value,canonicalize, and the CLI).serialize_valueformats structured Python values and PEP 750Templatestrings, andcanonicalizevalidates and normalizes raw CSS token streams (including relative color syntax and context-free CSS Values 4 math simplification) against@webref/cssgrammars. All three entry points resolve standard CSS properties, custom properties (--*),@at-ruledescriptors (@font-face/srcorat_rule="@font-face"), and<type>syntax targets; unknown<...>targets are rejected withStixisValidationError(exit4on the CLI). - Gates experimental CSS properties by engine support (
SerializationOptions(min_engines=...)and--min-engines). By default (SerializationOptions(min_engines=2)), Stixis enables CSS properties shipped unprefixed in at least 2 core browser engines (Blink, Gecko, WebKit) according to@mdn/browser-compat-data, plus an explicit allowlist incodegen/spec_patches.toml. PassSerializationOptions(min_engines=1),extra_properties=frozenset({"prop-name"}), or--min-engines 1(0..3) on the CLI to opt into single-engine properties. - Validates AST nodes and primitives on construction.
FontFace,StyleRule,AtRule,StyleSheet,CommentNode,RawDeclaration,FontSrc, andUrlare frozen, hashable dataclasses (dataclass(frozen=True, slots=True)) that validate selectors, preludes, descriptors, declarations, comments, and URL modifiers in__post_init__, snapshot mutable container inputs into immutable tuples, and raiseStixisValidationErrororStixisSyntaxErroron invalid input. - Escapes PEP 750
Template(t"...") slots by context (serialize_valueand AST nodes). Passing aTemplatetoserialize_valueor an AST node field (FontFace,StyleRule,AtRule,CommentNode) escapes comments (/* ... */), strings,url(...), and numbers according to the surrounding CSS token context and validates the rendered tokens against the target grammar. - Handles five CSS microsyntaxes.
<urange>:parse_urange,format_urange, andcompact_urangefor cascade-shadowing range compaction.<opentype-tag>:font-feature-settings,font-variation-settings, andfont-language-override.<font-src-list>:FontSrc,Url, andLocalFont, enforcingformat()beforetech().<family-name>and<generic-family>: quotes Pythonstrfamily names by default, requiresgeneric("serif")orKeyword("serif")for generic families, and matches WPTfont-family-serialization-001.html.{1,2}descriptor and property ranges:font-weight,font-widthandfont-stretch,font-style: oblique, and metric overrides.
- Includes a read-only CLI.
stixis inspectandstixis checkexit with0for valid input,1for a validation error,2for a usage error, and4for an unknown target.
Only the top-level stixis package is public; submodules such as stixis._syntax, stixis._grammar, stixis.values, stixis.stylesheet, and stixis.api are internal and not part of the public API.
from stixis import (
Deg,
FontFace,
FontSrc,
Pct,
Px,
SerializationOptions,
StyleRule,
StyleSheet,
compact_urange,
dumps,
generic,
)
if __name__ == "__main__":
woff2_url = "https://example.com/fonts/noto-sans-jp-slice0.woff2"
slice_id = "kana"
size_kb = 18.4
urange = compact_urange(
sorted_target_cps=[0x3000, 0x3041, 0x3042, 0x3044],
forbidden_sorted=[0x3043],
max_gap=0x80,
)
sheet = StyleSheet(
header_comment="Generated by Stixis",
rules=[
FontFace(
comment=t"Slice [{slice_id}] ({size_kb:.1f} KB)",
font_family="Noto Sans JP",
font_style=("oblique", Deg(-10), Deg(20)),
font_weight=(100, 900),
font_width=(Pct(75), Pct(125)),
font_display="swap",
src=t"url({woff2_url}) format(woff2) tech(variations)",
unicode_range=urange,
),
StyleRule(
"body",
{
"font_family": ["Noto Sans JP", generic("sans-serif")],
"font_feature_settings": {"liga": True, "dlig": False},
"margin": Px(0),
},
),
],
)
print(dumps(sheet))
print(dumps(sheet, options=SerializationOptions.cleome()))# Inspect an at-rule, property, or <type>
uv run stixis inspect @font-face
uv run stixis inspect font-style --json
uv run stixis inspect "<font-src>"
# Validate and canonicalize CSS values (--min-engines accepts 0..3)
uv run stixis check font-weight "100 900" --at-rule @font-face
uv run stixis check unicode-range "u+3040-309f" --at-rule @font-face
uv run stixis check hanging-punctuation "first" --min-engines 1codegen/gen_wpt_tests.py generates the WPT parsing and serialization test modules in tests/_generated/.
uv sync
uv run python codegen/gen_specs.py --check --webref-dir .upstream/webref/ed/css --bcd-dir .upstream/bcd/css
uv run python codegen/gen_wpt_tests.py --check --wpt-dir .upstream/wpt/css
uv run pytest
uv run ruff check