Skip to content

Palettes & styling

All built in widgets use a central color palette, referencable within TIM code. Each widget will use appropriate shades of the palette by default.

Since this palette is defined globally, you can also re-generate it with new colors. It only requires a primary color, but you may give it as many as you please.

Here is the default palette:

pytermgui/palettes.py                                                                                                         -3            -2            -1         primary          +1            +2            +3         #313a53       #4a587c       #6375a6       #7c93d0       #96a8d9       #b0bee2       #cad3ec                                                                                                                                                                                                              -3            -2            -1        secondary         +1            +2            +3         #53313a       #7c4957       #a66274       #d07b92       #d995a7       #e2afbd       #eccad3                                                                                                                                                                                                              -3            -2            -1        tertiary          +1            +2            +3         #3a5331       #587c49       #75a662       #93d07b       #a8d995       #bee2af       #d3ecca                                                                                                                                                                                                              -3            -2            -1         accent           +1            +2            +3         #534a31       #7c6f49       #a69462       #d0b97b       #d9c795       #e2d5af       #ece3ca                                                                                                                                                                                                              -3            -2            -1         surface          +1            +2            +3         #191a20       #252830       #323540       #3f4350       #656873       #8b8e96       #b2b3b9                                                                                                                                                                                                              -3            -2            -1        surface2          +1            +2            +3         #20191a       #302528       #403235       #503f43       #736568       #968b8e       #b9b2b3                                                                                                                                                                                                              -3            -2            -1        surface3          +1            +2            +3         #1a2019       #283025       #354032       #43503f       #687365       #8e968b       #b3b9b2                                                                                                                                                                                                              -3            -2            -1        surface4          +1            +2            +3         #201e19       #302d25       #403c32       #504b3f       #736f65       #96938b       #b9b7b2                                                                                                                                                                                                              -3            -2            -1         success          +1            +2            +3         #2b533c       #417c5a       #57a678       #6dd097       #8ad9ab       #a7e2c0       #c4ecd5                                                                                                                                                                                                              -3            -2            -1         warning          +1            +2            +3         #505035       #787950       #a0a16b       #c9ca86       #d3d49e       #dedfb6       #e9e9ce                                                                                                                                                                                                              -3            -2            -1          error           +1            +2            +3         #503035       #784950       #a0616b       #c97a86       #d3949e       #deafb6       #e9c9ce                                                                                                     

As you can see, each color name has a set of negatively and positively shaded variants. These can be accessed in the format: {color_name}{shade_offset}, such as primary+2 or surface2-1. The "main" color can be referred to without an offset, like accent or success.

Each color also has a background variant assigned to it, which may be used by prefixing the color-of-interest by an at symbol (@), like @error or @secondary+3.

Warning

Since the underlying colors may change between runs, it's best to make sure contrast ratios are met at all times. This is possible in PTG using the #auto markup pseudo-tag.

As mentioned later, the library will try to append this tag to any widget style without a set foreground color. To use it outside of a widget (or to make absolute certain that it applies), add it to your markup:

from pytermgui import tim

tim.alias_multiple(**{"@my-surface1": "@white", "@my-surface2": "@black"})
tim.print("[@my-surface1 #auto] Black on white [@my-surface2 #auto] White on black ")

 Black on white   White on black 

Styling widgets

As mentioned above, all widgets come with a basic, aesthetically pleasing set of styles. Sometimes though, you might wanna get a fresher look.

Modifying the palette

The easiest way to change up the look of your apps is by manipulating the global palette. This will change the colors of all built-in widgets, and all custom ones (so long as they use the palette colors).

This can be done using the Palette.regenerate method:

from pytermgui import palette

palette.regenerate(primary="skyblue")

Under the hood, it will look at the given color, and generate some complementaries for it.

The above code gives use the following palette, by the way:

                                                                                                        -3            -2            -1         primary          +1            +2            +3         #36525e       #517b8d       #6ca4bc       #87ceeb       #9fd7ef       #b7e1f3       #cfebf7                                                                                                                                                                                                              -3            -2            -1        secondary         +1            +2            +3         #5e3652       #8d517b       #bc6ca4       #eb87cd       #ef9fd7       #f3b7e1       #f7cfeb                                                                                                                                                                                                              -3            -2            -1        tertiary          +1            +2            +3         #525e36       #7b8d51       #a4bc6c       #ceeb87       #d7ef9f       #e1f3b7       #ebf7cf                                                                                                                                                                                                              -3            -2            -1         accent           +1            +2            +3         #5e4136       #8d6251       #bc836c       #eba487       #efb69f       #f3c8b7       #f7dacf                                                                                                                                                                                                              -3            -2            -1         surface          +1            +2            +3         #1a1f22       #272f33       #343f44       #414f55       #677277       #8d9599       #b3b8bb                                                                                                                                                                                                              -3            -2            -1        surface2          +1            +2            +3         #221a1f       #33272f       #44343f       #55414f       #776772       #998d95       #bbb3b8                                                                                                                                                                                                              -3            -2            -1        surface3          +1            +2            +3         #1f221a       #2f3327       #3f4434       #4f5541       #727767       #95998d       #b8bbb3                                                                                                                                                                                                              -3            -2            -1        surface4          +1            +2            +3         #221c1a       #332a27       #443834       #554741       #776b67       #99908d       #bbb5b3                                                                                                                                                                                                              -3            -2            -1         success          +1            +2            +3         #2c5a3f       #43875f       #59b47f       #70e29f       #8ce7b2       #a9edc5       #c5f3d8                                                                                                                                                                                                              -3            -2            -1         warning          +1            +2            +3         #525838       #7b8455       #a4b071       #cddc8e       #d7e3a4       #e1eabb       #ebf1d1                                                                                                                                                                                                              -3            -2            -1          error           +1            +2            +3         #523838       #7b5455       #a47071       #cd8c8e       #d7a3a4       #e1babb       #ebd1d1                                                                                                     

Note

Using highly saturated colors as for the primary color will likely result in harsh looking styles. Even if you are tied to a certain neon-looking HEX color, it is usually nicer to use some toned-down version of it as the primary, and possibly including it as the secondary, tertiary or accent arguments.

Modifying styles

If just changing the base colors isn't enough, you can add more involved styles on a per-widget basis.

Assigning styles is done through each widget's StyleManager, which can be accessed using widget.styles.

Properties can be set in a couple of ways. The simplest one uses the dot syntax:

widget.styles.label = my_style

You may also assign styles in the same statement as a widget's creation if you are pressed on space, by calling the styles property with a set of keyword arguments:

widget = MyWidget().style(label=my_style)

Finally, it is possible to assign multiple styles to the same value in one statement. This can be done by separating each key with 2 underscore characters, and works for either of the above methods:

widget.styles.border__corner = my_border_style
widget2 = MyWidget().styles(border__corner=my_border_style)

All the above examples modify styles pre-defined by the widget at declaration time. To see all the built-in styles, see the section on built-in widgets.

Values for these calls can be of 2 general types:

TIM string shorthands

The easier way to create custom styles is by defining some markup for them. A fully expanded markup string contains both the {depth} and {item} template keys, but it can be shortened and expanded automatically for convenience.

item must be in the final style string. It represents the string that was passed for styling. depth is not necessary to use; it represents the given widget's depth property.

For example, surface+2 italic dim would be expanded into [surface+2 italic dim]{item}, while [!gradient(210) bold]{item} would remain untouched.

Info

Under the hood, these strings will create MarkupFormatter instances, which perform the formatting and parsing when called.

TL;DR: This method is only syntactic sugar over the one below.

Custom callables

For more granular control of a style, one may use a callable. These follow the following signature:

def my_style(depth: int, item: str) -> str:

...where depth is an integer related to the styled widget, and item is the text in need of styling. The return of this is always assumed to be parsed, ANSI-coded text.

This method is far more powerful than just using template string, but it's also a lot more noisy to write. Here is a cool button style:

from pytermgui import Button, Container, pretty, tim

template = "[@primary #auto] {first} [@surface-1 #auto] {second} "


def split_style(_: int, item: str) -> str:
    if "<SPLIT>" not in item:
        return item

    first, second = item.split("<SPLIT>")
    return tim.parse(template.format(first=first, second=second))


Button.styles.label = split_style
Button.set_char("delimiter", [""] * 2)
my_button = Button("Hey<SPLIT>There")

pretty.print(my_button)

This gives the following result. See the #auto tag setting valid foregrounds for both light and dark backgrounds:

docs/src/widgets/style_callables.py Button (label= "Hey<SPLIT>There" , padding= 0 , centered= False )  Hey   There