Custom styles#
bootstyle covers the looks the themes ship. When you need something it can’t
name — a bespoke shape, your own element tree, a one-off tweak — a public toolkit
lets you build a ttk style yourself and apply it with style=.
Tweak an existing style#
The simplest custom style is a named variant of a base one. style.configure
a dotted name, then hand it to a widget with style=; the name inherits from
its base class (my.TButton → TButton), so you only set what changes:
import ttkbootstrap as ttk
app = ttk.App()
app.style.configure("big.TButton", font=("Helvetica", 16), padding=12)
ttk.Button(app, text="Big", style="big.TButton").pack()
app.mainloop()
Change an option everywhere#
To change a look for every widget of one kind, configure its base style. The change reaches the colored variants too, and it survives a theme switch:
import ttkbootstrap as ttk
app = ttk.App(theme="bootstrap-light")
app.style.configure("TEntry", padding=8)
ttk.Entry(app).pack(padx=10, pady=4)
ttk.Entry(app, bootstyle="danger").pack(padx=10, pady=4)
app.mainloop()
Both entries are padded — the plain one and the danger one. Set the option
once on the base class and every variant built from it picks it up.
To change one variant only, configure that name instead. The more specific name
wins, so this pads every entry by 8 except the danger ones:
app.style.configure("TEntry", padding=8)
app.style.configure("danger.TEntry", padding=20)
It also works the other way round: you can set the option after the widgets already exist, and they update.
Colors are the exception. A color you set this way applies immediately but is
not carried across a theme switch — colors keep following the theme, so a
bootstrap-light entry does not stay light after switching to
bootstrap-dark. To change the colors a theme uses, see
Theming & Colors.
To go back to the shipped look, drop the option and let the next rebuild restore it:
app.style.reset_style_options("TEntry") # one style
app.style.reset_style_options() # everything you have set
Options a widget doesn’t read#
A style option only does something if the widget actually reads it. A few don’t, and the setting is quietly ignored — the option is stored, it just never reaches the screen. These are the ones worth knowing:
Setting |
What to do instead |
|---|---|
|
These take their font from the widget, not the style. Pass it to the widget:
ttk.Entry(app, font="-size 14")Or change the named font they use,
TkTextFont |
|
The sash is drawn from a single global style, so it cannot be set per widget — every panedwindow shares one thickness. Configure that style:
app.style.configure("Sash", sashthickness=6) |
Build a style from scratch#
Advanced
This is an advanced feature. It works directly with ttk’s own element and layout model — element trees, element options, and state specs — which this guide does not teach. The toolkit below makes that model easier to drive, but assumes you’re comfortable with how ttk styling works. For the underlying concepts, see the tkinter.ttk styling reference.
For a genuinely new look, compose one from assets, elements, a state map, and a layout. Each piece is a small toolkit call:
Assetsrenders cached images from recipes (circle,rounded_rect,rect,icon) or animagedraw callback. Sizes are logical UI units — DPI scaling is handled for you.image_elementturns an image into a named ttk element (optionally state-keyed).state_mapis a validatedstyle.map— a typo in a state string raises instead of silently doing nothing.layoutarranges elements into the widget’s element tree withElnodes, and registers the style for you.
import ttkbootstrap as ttk
from ttkbootstrap import Assets, El, layout, image_element, state_map
app = ttk.App()
style = app.style
assets = Assets(style)
pill = assets.rounded_rect(style.colors.primary, size=(80, 28), radius=14)
image_element(
style,
"Pill.TButton.background",
default=pill,
border=14,
sticky="nsew"
)
state_map(
style,
"Pill.TButton",
foreground={"disabled": style.colors.border}
)
layout(style, "Pill.TButton",
El("Pill.TButton.background", sticky="nsew", children=[
El("Button.label", side="left", expand=True)])
)
ttk.Button(app, text="Go", style="Pill.TButton").pack()
app.mainloop()
Because layout registers the name, style="Pill.TButton" resolves to what
you built. If you configure a style but never call layout on it, register it
yourself with register_style so ttkbootstrap doesn’t re-resolve the name back
to the base.
Icons#
The same asset pipeline renders Bootstrap Icons glyphs. To put an icon on a
widget — the icon= keyword, the imperative apply_icon, or the standalone
Icon image — see the Icons guide.
What belongs here is placing a glyph inside a style layout you build
yourself: icon_element creates a ttk image element whose per-state image is a
glyph, so a custom indicator (a checkbox, a toggle) can be drawn from the font
instead of a raster asset. Unlike image_element it takes glyph names (not
pre-rendered images), a required size=, and an element name of the form
<ttkstyle>.<element> — the glyph color follows that style’s foreground. Give a
default glyph plus a states map of state → glyph:
from ttkbootstrap import icon_element, layout, El
icon_element(
style,
"Star.TCheckbutton.indicator",
size=18,
default={"name": "star", "color": "warning"},
states={"selected": "star-fill"},
sticky="w",
)
layout(style, "Star.TCheckbutton",
El("Star.TCheckbutton.indicator", side="left", children=[
El("Checkbutton.label", side="left", expand=True)])
)
ttk.Checkbutton(app, text="Favorite", style="Star.TCheckbutton").pack()
Creating theme-aware styles#
A custom style is built against the active theme, so a theme switch repaints
the built-in widgets but leaves your style untouched — its colors go stale. Wrap
the build in @theme_aware and it re-runs after every switch, rebuilding
against the new theme’s style.colors:
from ttkbootstrap import theme_aware, Assets, El, layout, image_element
@theme_aware # runs once now, and again after every theme change
def build_pill(style):
fill = style.colors.primary
pill = Assets(style).rounded_rect(fill, size=(80, 28), radius=14)
image_element(
style,
"Pill.TButton.background",
default=pill,
border=14,
sticky="nsew"
)
layout(style, "Pill.TButton",
El("Pill.TButton.background", sticky="nsew", children=[
El("Button.label", side="left", expand=True)])
)
Now ttk.Button(app, style="Pill.TButton") stays correct through
theme_use and toggle_theme. theme_aware even works at module top
level, before the app exists — the build is queued and runs when the app is
created. Use on_theme_change(fn) for the plain-function form, and
remove_theme_change_callback(fn) to unregister.
See also
Styling reference — the
Assets/El/layout/image_element/register_styleAPI.Theming & Colors — the
style.colorspalette custom styles draw from.Icons — putting a glyph on a widget, rather than inside a style layout.