Styling

Click Extra ships its own Style class as a drop-in replacement for cloup.Style (which itself wraps click.style). The runtime contract — calling the instance to apply styling, equality, hashing, with_() — is identical to cloup’s; everything below is purely additive ergonomics.

The class lands automatically when you do from click_extra import Style: __init__.py re-exports it after from cloup import * so the click-extra version takes precedence.

hello

Hex-string color shorthand

Style.fg and Style.bg accept "#rrggbb" or "#rgb" shorthand strings alongside Click’s named colors and RGB tuples. The shorthand is converted to an RGB tuple on construction:

sample
sample
sample
sample

REPL-friendly __repr__ and __str__

The compact __repr__ hides None and falsy attributes and renders RGB tuples as #rrggbb. The __str__ returns the styled word "sample" so print(style) and debugger inspectors visualize what the style does, not just its fields:

Style(fg=#f1fa8c, bold, italic)
sample

Composition operator a | b

The | operator merges two styles. The right operand wins on conflicts; None fields on either side don’t override the other side’s set fields. The reflected __ror__ makes mixing with cloup.Style (or any compatible subclass) symmetric:

Style(fg='magenta', bold, italic)
Style(fg='cyan', bold, italic)

cascade(base) — fill gaps from a base style

Where | lets the right operand always win, cascade keeps the instance’s set values and only fills its None fields from base. Useful for theme-inheritance patterns where a derived style should keep its overrides and inherit the rest:

Style(fg='magenta', bold, italic)

TOML/JSON round-trip with to_dict / from_dict

Style.to_dict() emits a plain mapping containing only the set fields, with RGB tuples flattened to #rrggbb strings so the output round-trips through TOML/JSON/YAML untouched. Style.from_dict() is the symmetric loader and rejects unknown keys with TypeError so typos surface immediately:

{'fg': '#f1fa8c', 'bold': True}
{"fg": "#f1fa8c", "bold": true}
True
TypeError('Unknown Style field(s): colour')

to_css() — CSS declaration list

Renders the style as a semicolon-separated CSS declaration list, suitable for inline style="..." attributes on HTML spans. Used by AnsiHtmlFormatter and the theme palette swatches:

color: #f1fa8c; font-weight: bold; font-style: italic
color: #ff5555; text-decoration: underline line-through
color: cyan; opacity: 0.6

The mapping is:

Attribute

CSS declaration

fg

color: <color>

bg

background-color: <color>

bold

font-weight: bold

italic

font-style: italic

underline

text-decoration: underline

overline

text-decoration: overline

strikethrough

text-decoration: line-through

dim

opacity: 0.6

reverse

filter: invert(1)

underline, overline and strikethrough collapse into a single text-decoration declaration when more than one is set.

from_ansi() — parse ANSI SGR escapes

Given one or more consecutive ANSI SGR escapes (the \x1b[...m sequences Click emits), rebuild a Style instance. Supports the standard 8/16-color codes (30–37, 40–47, 90–97, 100–107), the 38;5;n / 48;5;n 256-color extension, and the 38;2;r;g;b / 48;2;r;g;b 24-bit extension. Reset codes (0) are ignored. Multiple back-to-back escapes (as Click emits when combining colors with attributes) are merged into a single Style:

Style(fg='red', bold)
Style(fg='red', bold)
Style(fg=#f1fa8c)
Style(fg=226)

from_ansi is the inverse of calling the style: parsing the output of Style(fg="red", bold=True)("text") recovers the same style.

contrast_ratio(other) — WCAG accessibility check

Returns the WCAG 2.x contrast ratio between this style’s foreground and another style’s foreground. Result is in [1, 21]: 1 = identical colors (no contrast), 21 = maximum contrast (black on white). WCAG AA requires 4.5+ for normal text and 3.0+ for large text; AAA wants 7.0+ and 4.5+ respectively.

9.04
1.23
4.08

Click Extra uses this internally to gate the WCAG legibility floor and AA Large compliance for branded themes.

Equality and hash

Style overrides cloup’s __eq__ and __hash__ to skip the lazily-populated _style_kwargs cache, so two otherwise-identical styles compare equal whether or not either has been called yet:

True
True
True

Shared dataclass round-trip helpers

The serialization machinery Style.to_dict / from_dict relies on three small module-level helpers that codify “walk dataclass fields, skip the cloup _style_kwargs cache, skip default-valued fields, raise on unknown keys”. They’re public so other dataclass-shaped values in click-extra (notably HelpExtraTheme) can reuse them, and so downstream code with similar patterns can build on the same primitives.

fields_to_dict(instance, *, encode=…, keep=…)

Walks every field via dataclasses.fields, applies an optional keep(field, value) -> bool filter (default keeps every non-default field), and passes the surviving values through an encode(field, value) -> encoded_value callback (default identity):

{'r': 255, 'g': 128, 'name': 'orange'}
{'r': 'ff', 'g': '80', 'name': 'orange'}

dict_to_fields(cls, data, *, decode=…)

The symmetric loader. Validates every key in data against cls’s dataclass fields and raises TypeError listing every unknown key, so callers can build a constructor call without an extra pre-validation pass:

Color(r=255, g=0, b=0, name='orange')
TypeError('Unknown Color field(s): red')

cascade_fields(base, overlay, *, is_set=…)

Slot-level analogue of Style.cascade’s attribute-level merge. Walks both instances’ fields and produces a kwargs dict suitable for type(base)(**kwargs) (or dataclasses.replace(base, **kwargs)):

Color(r=255, g=255, b=255, name='snow')

click_extra.styling API

        classDiagram
  Style <|-- Style
    

Drop-in replacement for cloup.Style with extra features.

The module name mirrors cloup.styling, the upstream module that hosts the original Style class. Click Extra’s Style is a subclass that keeps cloup’s runtime contract (calling, equality, hashing, with_()) intact and adds:

  • A compact, single-line __repr__ that hides None and falsy attributes and renders RGB tuples as #rrggbb hex.

  • Hex-string color shorthand: Style(fg="#f1fa8c") works alongside Style(fg=(241, 250, 140)).

  • A __str__ that returns the styled word "sample" so REPL prints and debuggers visualize what the style does, not just its fields.

  • A composition operator a | b that merges two styles, with the right operand winning on conflicts.

  • A Style.cascade() method that fills the style’s None fields from a base style without overriding any value already set.

  • Style.to_dict() / Style.from_dict() for round-tripping styles through TOML/JSON/YAML.

  • Style.to_css() for emitting CSS-equivalent declarations: useful for HTML renderings of help screens.

  • Style.from_ansi() for parsing an ANSI SGR escape sequence back into a Style instance.

  • Style.contrast_ratio() returning the WCAG contrast ratio between two foreground colors. Useful for theme designers checking accessibility.

click_extra.styling.fields_to_dict(instance, *, encode=<function <lambda>>, keep=<function <lambda>>)[source]

Serialize a dataclass instance to a dict of set fields.

Walks every field via dataclasses.fields(), skips the internal _style_kwargs cache, applies keep to decide which fields are written (default: every non-default field), and passes the surviving values through encode (default: identity).

Parameters:
  • instance (Any) – the dataclass to serialize.

  • encode (Callable[[Any, Any], Any]) – callable (field, value) -> encoded_value applied to every kept value. Use to convert RGB tuples to #rrggbb strings, nested dataclasses to dicts, etc.

  • keep (Callable[[Any, Any], bool]) – callable (field, value) -> bool deciding whether the field is emitted. Default keeps everything that differs from the field’s declared default.

Return type:

dict[str, Any]

click_extra.styling.dict_to_fields(cls, data, *, decode=<function <lambda>>)[source]

Validate data’s keys against cls’s dataclass fields and decode them.

Returns a kwargs dict ready to splat into cls(**kwargs). Raises TypeError listing every unknown key, so callers can build a constructor call without an extra pre-validation pass.

Parameters:
  • cls (type) – the dataclass type whose fields define the legal keys.

  • data (dict[str, Any]) – mapping from field name to a serialized value.

  • decode (Callable[[Any, Any], Any]) – callable (field, raw) -> decoded_value invoked for every recognized key. Default returns the raw value unchanged.

Return type:

dict[str, Any]

click_extra.styling.cascade_fields(base, overlay, *, is_set=<function <lambda>>)[source]

Layer overlay’s set fields on top of base, returning a merged kwargs dict.

Walks both instances’ fields and produces a dict suitable for type(base)(**kwargs) (or dataclasses.replace(base, **kwargs)). The slot-level analogue of Style.cascade’s attribute-level merge.

Parameters:
  • base (Any) – the underlying instance whose fields fill any gaps.

  • overlay (Any) – the instance whose set fields win on conflicts.

  • is_set (Callable[[Any, Any], bool]) – callable (field, value) -> bool distinguishing “set” from “unset” fields. Default treats a value equal to the field default as unset.

Return type:

dict[str, Any]

class click_extra.styling.Style(fg=None, bg=None, bold=None, dim=None, underline=None, overline=None, italic=None, blink=None, reverse=None, strikethrough=None, text_transform=None)[source]

Bases: Style

cloup.Style with extra ergonomics.

See the module docstring for the full list of additions. The runtime contract (calling the instance to apply styling, equality, hashing, with_()) is otherwise identical to cloup.Style.

fg: str | tuple[int, int, int] | int | None = None

Foreground color: named ANSI string, #rrggbb hex, RGB tuple, or palette index.

bg: str | tuple[int, int, int] | int | None = None

Background color: named ANSI string, #rrggbb hex, RGB tuple, or palette index.

cascade(base)[source]

Return a copy with None fields filled in from base.

The instance’s own non-None values always win: cascade only fills gaps. Useful for theme inheritance: derived.cascade(parent) keeps derived’s overrides and inherits the rest from parent.

Return type:

Style

to_dict()[source]

Serialize to a plain dict with only the set fields.

RGB tuples are emitted as #rrggbb strings so the result round-trips through TOML/JSON/YAML untouched. Pair with from_dict() to rebuild a Style.

Return type:

dict[str, Any]

classmethod from_dict(data)[source]

Build a Style from the plain dict produced by to_dict().

Validates that every key in data names a known Style field and raises TypeError otherwise. Pair with to_dict() to round-trip through TOML/JSON/YAML.

Return type:

Style

to_css()[source]

Render the style as a semicolon-separated CSS declaration list.

Style(fg="#f1fa8c", bold=True).to_css() returns "color: #f1fa8c; font-weight: bold". Suitable for inline style="..." attributes on HTML spans.

Return type:

str

classmethod from_ansi(escape)[source]

Parse one or more consecutive ANSI SGR escapes into a Style.

Supports the standard 8/16-color codes (30–37, 40–47, 90–97, 100–107), the 38;5;n / 48;5;n 256-color extension, and the 38;2;r;g;b / 48;2;r;g;b 24-bit extension. Reset codes (0) are ignored. Multiple back-to-back escapes (as click emits when combining colors with attributes: \x1b[31m\x1b[1m) are merged into a single Style.

Return type:

Style

contrast_ratio(other)[source]

Return the WCAG 2.x contrast ratio between this fg and other’s fg.

Result is in [1, 21]: 1 = identical colors (no contrast), 21 = maximum contrast (black on white). WCAG AA requires 4.5+ for normal text, 3.0+ for large text; AAA wants 7.0+ and 4.5+ respectively.

Return type:

float