Skip to content

Textual CSS

Textual uses CSS to apply style to widgets. If you have any exposure to web development you will have encountered CSS, but don't worry if you haven't: this chapter will get you up to speed.

VSCode User?

The official Textual CSS extension adds syntax highlighting for both external files and inline CSS.

Stylesheets

CSS stands for Cascading Stylesheet. A stylesheet is a list of styles and rules about how those styles should be applied to a web page. In the case of Textual, the stylesheet applies styles to widgets, but otherwise it is the same idea.

Let's look at some Textual CSS.

Header {
  dock: top;
  height: 3;
  content-align: center middle;
  background: blue;
  color: white;
}

This is an example of a CSS rule set. There may be many such sections in any given CSS file.

Let's break this CSS code down a bit.

Header {
  dock: top;
  height: 3;
  content-align: center middle;
  background: blue;
  color: white;
}

The first line is a selector which tells Textual which widget(s) to modify. In the above example, the styles will be applied to a widget defined by the Python class Header.

Header {
  dock: top;
  height: 3;
  content-align: center middle;
  background: blue;
  color: white;
}

The lines inside the curly braces contains CSS rules, which consist of a rule name and rule value separated by a colon and ending in a semicolon. Such rules are typically written one per line, but you could add additional rules as long as they are separated by semicolons.

The first rule in the above example reads "dock: top;". The rule name is dock which tells Textual to place the widget on an edge of the screen. The text after the colon is top which tells Textual to dock to the top of the screen. Other valid values for dock are "right", "bottom", or "left"; but "top" is most appropriate for a header.

The DOM

The DOM, or Document Object Model, is a term borrowed from the web world. Textual doesn't use documents but the term has stuck. In Textual CSS, the DOM is an arrangement of widgets you can visualize as a tree-like structure.

Some widgets contain other widgets: for instance, a list control widget will likely also have item widgets, or a dialog widget may contain button widgets. These child widgets form the branches of the tree.

Let's look at a trivial Textual app.

from textual.app import App


class ExampleApp(App):
    pass


if __name__ == "__main__":
    app = ExampleApp()
    app.run()

ExampleApp

This example creates an instance of ExampleApp, which will implicitly create a Screen object. In DOM terms, the Screen is a child of ExampleApp.

With the above example, the DOM will look like the following:

eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nM1Ya0/jOFx1MDAxNP3Or6i6X3YlXGKOY8fxSKtcdTAwMTXPpSywo1x1MDAwMVxyj9VcYrmJaT3Na1x1MDAxMpfHIP77XqdMXHUwMDFlbVxiZVx1MDAxN0ZEUZv4OtfX1+fc4+R+pdfr67tU9j/0+vLWXHUwMDE3oVxuMnHTXzXt1zLLVVx1MDAxMoNcdFx1MDAxN/d5Ms38oudY6zT/sL5cdTAwMWWJbFwidVx1MDAxYVxuX1rXKp+KMNfTQCWWn0TrSsso/8P8XHUwMDFliUj+niZRoDOrXHUwMDFhZE1cdTAwMDZKJ9lsLFx1MDAxOcpIxjpcdTAwMDfv/8B9r3df/NaiXHUwMDBilIiSOCi6XHUwMDE3hlp4njvfepTERaiUIVx1MDAwN3PqkbKDyrdhMC1cdTAwMDOwXkHAsrKYpn56sbN1pD45eiNcdTAwMWKyfNP5Nvy6d1WNeqXC8FjfhbM8XGJ/PM1kZc11lkzkqVxu9Fx1MDAxOOz2XFx7+VxcnkBcbqqnsmQ6XHUwMDFhxzLPXHUwMDFizySp8JW+M21cYpWtXCJcdTAwMWVcdTAwMTU+qpZbk1x1MDAwMeJamHmO5zpcdTAwMGV1XHUwMDEwqc23cECY5VLsXHUwMDEwh9G5mLaSXHUwMDEw1lx1MDAwMGL6XHUwMDA1XHUwMDE1R1x1MDAxNdVQ+JNcdTAwMTGEXHUwMDE2XHUwMDA3VVx1MDAxZlx1MDAwZvvcrs335sdMa1x1MDAwM46lXHUwMDFhjbVpxNjyXHUwMDEwcT1GZ75r+ZBF/m3P5pRcdTAwMTKMcWkxI6aDoFx1MDAwMMKX+fyNRZY+5qmfm5tatCbQnXlcdTAwMTTVkVRbY+dcIkv5vlx1MDAxYVxmvk7GfyX88CxcdTAwMWRcdTAwMGZcdTAwMGVLX1xy2Gl5q/ul4WG1y+2Ze1x1MDAxMm1cdTAwMGUv7evp9v6BPls7+8jRfrtbkWXJzfN+XHUwMDFiUawuO5HK7eNVlchpXHUwMDFhiFx1MDAxOfZt10XE5sjjXHUwMDBl4aU9VPFcdTAwMDSM8TRcZqu2xJ9UdFmpxbtA0kacdYba5CmG2thQXHUwMDE0XHUwMDEw4i1N0e7le69cdTAwMTSldidFObeAXG6GLP+HoTpcdTAwMTNxnopcZljQwlLWxlK+wErmeraDXFxcdTAwMWK9Piu7kMihOr1cdTAwMDSJ1YInsT5W31x1MDAwYjS5XHUwMDE2hWKEsIsw41x1MDAxY1HW6LUrXCJcdTAwMTXeNdawgCxEvnMrojSUXHUwMDFiafrrb/VcdTAwMTTnXHUwMDEyXCIpXFyTxjNcdTAwMWKhXHUwMDFhXHUwMDE5aPd9mJvMXHUwMDFhqNdcbkSu7Fx1MDAxMKkgXGJrXGL0IVx1MDAxMFx1MDAwMT6zwTKCk2RqpGJcdTAwMTGetMXZScZM+nqGxVx1MDAxNkZS+qRmYlx1MDAwNCDkUJXdpVx1MDAxOXn+PdGXXyfDk+PRwblzQsefkvPLd89IXHUwMDE3W8hlhHheXHUwMDFiI1x1MDAxZNuxXHUwMDEwI9h+U0pSukhJj0GlmFx1MDAxM+tHalx1MDAwMqRcdTAwMTHFXHUwMDFlcV+fml3KXHUwMDE27MfnQ0rOXHUwMDBmtlx1MDAwMrw33tldu9zDn9+jYM78nu5/vr45INuHXHUwMDA3XHUwMDE5XHL+vMNTTLbdV/CLT4PB3u7EP/Q2iH1cdTAwMTKFf+/EXHUwMDE3ozdcdTAwMTX49sS/QOCZkVZe7a/eSOBcdPXmW3+UXHUwMDEzwinUYUKX34J3o+3dVlx1MDAxM9ZZTVxisZhdaNzbXHUwMDE1XHUwMDEz0lJMsDNfREBcdTAwMWFhXHUwMDE3wp2fKu8vx2GbvGPUaO2Q82M/kzJ+SspZo/+rSfkzMjgv5WWMnZSbVZJcdTAwMTbOMfxcdTAwMTTlQCZAv+FcXF7Bu0vxO+Wc43BcdTAwMGJe7lx1MDAxMXNaOYdcdTAwMTm1XFzOjYJcdTAwMTNujjdjXHUwMDFlslxid5vkLlx06Fx1MDAxMIsz7FJcdTAwMTcvyLlcdTAwMDebXuDGf9loXHUwMDE3wf1sJuZaZHpTxYGKR2CslFxm2OhPzbhryEKO7VLCoVx1MDAxNlKOXHTyylmb6YnU7D0tXHUwMDAyckBcdTAwMWPYg1x1MDAxYYxWr5+98kNQ19b4sXMpqX1cdTAwMTlcdTAwMDfPXHUwMDA2hThUX8Tg1Vx1MDAwME7KmLdcdTAwMTBcdTAwMTW24LWh2HVcdTAwMTXfKmyHPVx1MDAxNVY7zVx1MDAxN8JcbkWut5IoUlx1MDAxYdL/MVGxnk9zkc9ccsPvsVx1MDAxNMG8XHUwMDE1plW3zVx1MDAxN4LUeGzu3KqrXsWU4qa8/rLa2nttXHUwMDExweaoYbfysFL/NzuQwmdfpOmxXHUwMDA2pJVrXHUwMDAwYFbBY+GuJta/VvJms+Xb0lVxmDRcdTAwMTYpNCVHmundP6w8/Fx1MDAwYlxiYlx1MDAxObwifQ== ExampleApp()Screen()

This doesn't look much like a tree yet. Let's add a header and a footer to this application, which will create more branches of the tree:

from textual.app import App, ComposeResult
from textual.widgets import Header, Footer


class ExampleApp(App):
    def compose(self) -> ComposeResult:
        yield Header()
        yield Footer()


if __name__ == "__main__":
    app = ExampleApp()
    app.run()

ExampleApp ExampleApp ^p palette

With a header and a footer widget the DOM looks like this:

eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1aa0/bSFx1MDAxNP3Or0DZL7tS4877UWm1glx1MDAxNpZ3aWFcdTAwMDNlVVWuPSReP2s7QKj633dsgu28XHUwMDFjXHUwMDEzXHUwMDEyNpXWQiGZmdy5nrnnzLk3/r6xudlKXHUwMDA3kWq92WypO8v0XHUwMDFjOzZvW6+y9lx1MDAxYlx1MDAxNSdOXHUwMDE46C6Uf07CfmzlI3tpXHUwMDFhJW9ev/bN2FVp5JmWMm6cpG96Sdq3ndCwQv+1kyo/+SN7PTF99XtcdTAwMTT6dlx1MDAxYVx1MDAxYuUkbWU7aVx1MDAxOD/MpTzlqyBNtPW/9efNze/5a8U72zH9MLDz4XlHxT0hx1tPwiB3XHUwMDE1Ulx1MDAwNLEkklx1MDAxNFx1MDAwM5zknZ4sVbbuvdZcdTAwMGWrsidralxyTi//7Jn/qItLwY5Odq7hh17nvJz12vG8s3TgPayDafX6sSp7kzRcdTAwMGVddeHYaS+bfKy9+F5cdTAwMTLqJSi/XHUwMDE1h/1uL1BJMvKdMDItJ1x1MDAxZOg2XG6KRjPo5ibKlrtsXHUwMDAxIDVcdTAwMTBcdTAwMTdYMIwpXHUwMDA2RJS3m31fSINRhFx05nTMo7ehp3dAe/RcdTAwMGLIr9Knr6bldrVjgV2OXHUwMDExyJKwcre3j/dZma+nnG4vzVx1MDAxYVx1MDAxMTJcdTAwMDQgTHD6YLuyXHUwMDFhKl99yCDjknAhip5sxmjfzsPg8/jq9cw4XHUwMDFhrlIryT5UvM1cdTAwMWPdXHUwMDE5j6FqXHUwMDFjVXbY/bgtrlx1MDAwZTBoR9euc9x7f+6eqm+FrZGgS9Vd2io6fryqM1x1MDAwYjto76vf+3JLOsHX+Pro+vh+f3u6WTOOw9umdpfu7sjoV00nLM1cdTAwMGXflfvTj2wzXHUwMDFkbilcdTAwMDNcdTAwMDTpXHUwMDAwpITgot9zXHUwMDAyV3dcdTAwMDZ9zyvbQsstMbhR8XdcdTAwMDL5I35WYVx1MDAwZsFM2DMuJEKYoca4r1/mNcU9XHUwMDAydbiHXHUwMDA0XHUwMDFhlOZcdTAwMTB8XHUwMDBl7tPYXGaSyIw1tqZgn0/Dvlx1MDAxY8c6oYBwKjFaPtSXXHUwMDE5h+V2h0F65tw/xJJBNcNcdTAwMDHEXHUwMDAw4lJcdTAwMDLKR0btmr7jXHJGdjBcdTAwMGZY7fnOnelHntqKol9/q65worQnuWky8p0tz+lmgd2y9L2peCTmU0efm8VcdTAwMDDfsW2vXHUwMDEyf5Z2xNQ24/0mZ1hcdTAwMTg7XScwvfNpftZCMVZW+lx1MDAxMIpT8EhcdTAwMTmcjcd87Vx1MDAwNGON8eh9ROdcdTAwMDcn51dXXHUwMDFjfGA+3btcbkkvXXc8YmhcdTAwMDDGXHRcdTAwMTFiXHUwMDFhXHUwMDFlXHUwMDExpVx1MDAwNuBcdTAwMDTBlVx1MDAwMpLSSUBcbq55YkxcdTAwMDA8XHUwMDFlwlx1MDAxNHLOKV9cdTAwMDEy6061I6ftfzlcdTAwMWFcdTAwMWO+9c47g0/3XHUwMDFkeb69dbK+h/DFQefm9oi8Oz6Kqf3nXHUwMDAw9Vx1MDAxMXnHlmBcdTAwMTdd2Pt7u651LLZcYjz3vfc7wVV3XHR2l76880TD9Fx0XHUwMDFiekturtt+6rZcdTAwMDdcdTAwMWbIXHUwMDE3c0dYd+q47y9hXHUwMDE1tlx1MDAwZb+ddtPww5dTR4pcdTAwMDO307uEnzrN7DZcdTAwMTE5XHUwMDE4gVx1MDAxMjUrXHUwMDEyOYTNXHUwMDE2OZhcbkkolOWIeaRaXHUwMDFmXHUwMDE260qqrJZUXHUwMDA1MzhcdTAwMDTymclNPaeSKZyKSmHxyKVQICQpYCtIaJZcdTAwMTmI01RcdTAwMGVcdTAwMDIjrTWq5syKlVxuZilcdTAwMWE+Mn5pimaOXHUwMDFhXHUwMDE4VzSFj7WYe8D8XHUwMDE00HExXHUwMDEzc1x1MDAxMFx1MDAxMEq0lm1eUKg/ktZcdTAwMTNzXHUwMDE4XGJDUlx1MDAwMTiejjnIXHImZSZkiMyulSFcdTAwMGZcdTAwMThEslFwXHUwMDE3XHUwMDAwxMSQXHUwMDFjMcrQpKrRniGh4bhcdTAwMDBcdTAwMTJz71x1MDAxNkWiwIwvgsQkNeN021x0bCfo6s7yLNNotPrZvG1gXHUwMDAwrNVcdTAwMWGRmlxmqURcdTAwMDSI4raz2zOjbGNcckKylIdSJlx0YkRWRlxmS2x1XHUwMDE5wnBwcai2VGDPdVxuSE2/gOtcZkn/UV5ip/Bcblx1MDAxOTp7ytVnXlx1MDAwN4KYz3JrOswn3PLMJH1cdTAwMWL6vpPq5T9ccp0gXHUwMDFkX+Z8PbcyfPeUaY/36tuq9o1cdTAwMTNBlFlcdTAwMWNVsOW7zVx1MDAxMir5h+L951dTR7cnQzi7KsFbWtio/l8oXHUwMDA3g1x1MDAwMM1OwpD2g2KEmlx1MDAxN0VOXHUwMDBmXHUwMDA3b4OrvnQv/Y8n9uG9+5f7z81/y1061uaQXHUwMDE31OSFMIJcdTAwMDRcdMr1a4XNM1x1MDAwM4RcdTAwMTBDY4Q9dldy0v84XHUwMDE304QlKIa0dOhFUjHn6F03OtlNXHUwMDBmLv0t92T70N/12zPU9/+p2NPtrmh5l252XoY3fcKG3j4jw3skxdnHblx1MDAwNqfKT0ArysQkJuOtXHUwMDE1ZoX64GWoeXmrfvvWlVkhrmVWzlxmJiBmkIFVM2uzjFxmccZYRqsvmpA9OVx1MDAxZZ+XkO1pXHUwMDE1o+JcdTAwMTdOyOYog/GErPDxXHUwMDE50oaBurSMIy2NafNSiJfsXHUwMDFlmGfx9o06oJ3j+z3w7XhcdTAwMWKsO1x1MDAwMDFhXHUwMDA2gZzAXHUwMDFjX9mvXHUwMDFiY9KGXHUwMDFiWId8MWBdlI3kOkmQ1VrWiyib40+nZF/sWIdcdTAwMDNcdTAwMTd+ci/aN1F/XHUwMDAw/1c2y1I2K1ren8XsPME0fcKG3q60dI04rXLNilx1MDAwNFx1MDAxM6SYjTdcdTAwMTeEzTHQvoAnKKb6/VtXwqawlrC5NCjEWFx1MDAwZVx1MDAwNdVcblx0u2FcdDvLPzlcdTAwMDGlmy8jmJ5cdTAwMTiPz1x1MDAxM0y7YZi+uGCaozfGXHUwMDA1U+FjLfRmVrBcdTAwMTma/UicoEBcdTAwMTJcZnDzXHUwMDEydn32tq7QXHUwMDAzwFx1MDAxMFRcYplVQ1x1MDAwNVx1MDAxNmhcdTAwMDR5WEslwXV+wIfIw6uDXHUwMDFlXHUwMDAyhqSMS0klg1JCMYlEgVxyqZNIJFx1MDAxONY+M4nGgUmAhFx1MDAxNEm0XHUwMDAwMJ9R0F48k5ld0G5Q8C2PuWqlmVBcdTAwMDBcdTAwMDGlXHUwMDAy6pVgXGLDyqjH6jdFXHUwMDE0wmH2KTBcdTAwMWVcdTAwMGWYX89cdTAwMWXxqT61XHUwMDE59YkhoONcZnFJXHUwMDAw1uGEJnyCyOA6USZMZ8Ukq1x1MDAxM6BcdKd+qmr27GDOrokwLu1tVP8/mc8gwLNcdI3RXGbmXFw011x1MDAxMvXqal1cdFxyXHUwMDBiXHUwMDAzScFcdTAwMDVikmnJUFx1MDAxZVRcdTAwMGaEpqVcdTAwMDTCNPuVnFxiQenqXGJNYoNBiHQ8M4xw5cHeks6kgVx1MDAwNOeSUo6ZoHLy2V/B9Z2QhTLCZ/HZokJj2XxcdTAwMDZcZk1j2lx1MDAxYlx1MDAwMfR2MSRk+ZxiwVx1MDAxZNyA+TOTXHUwMDEwPGzognxWrzxGfKJASL1OTGtcdTAwMDRcbinhXHUwMDEzLlx0gzJ9XGZcdTAwMDGYXHUwMDFmm1iIn5rNZlx1MDAwNXJ2TYTwLCrbXHUwMDE4mm+ZUXSW6ngrtkKHtGNcdTAwMGbVaXmPrVx1MDAxYkfdbk95vP46vzLBl69mxkIqu9PvPzZ+/Fx1MDAwYlx0sVx1MDAwYuIifQ== ExampleApp()Screen()Header()Footer()

What we didn't show

We've simplified the above example somewhat. Both the Header and Footer widgets contain children of their own. When building an app with pre-built widgets you rarely need to know how they are constructed unless you plan on changing the styles of individual components.

Both Header and Footer are children of the Screen object.

To further explore the DOM, we're going to build a simple dialog with a question and two buttons. To do this we're going to import and use a few more builtin widgets:

dom3.py
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal
from textual.widgets import Button, Footer, Header, Static

QUESTION = "Do you want to learn about Textual CSS?"


class ExampleApp(App):
    def compose(self) -> ComposeResult:
        yield Header()
        yield Footer()
        yield Container(
            Static(QUESTION, classes="question"),
            Horizontal(
                Button("Yes", variant="success"),
                Button("No", variant="error"),
                classes="buttons",
            ),
            id="dialog",
        )


if __name__ == "__main__":
    app = ExampleApp()
    app.run()

We've added a Container to our DOM which (as the name suggests) contains other widgets. The container has a number of other widgets passed as positional arguments which will be added as the children of the container. Not all widgets accept child widgets in this way. A Button widget doesn't require any children, for example.

Here's the DOM created by the above code:

eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1d+1PiyFx1MDAxNv59/oop95e9VWO2+/T7Vt265Vx1MDAwYnVcdTAwMWSfOFx1MDAwYnrnllx1MDAxNSFcdTAwMDIjLyGIurX/+z1cdTAwMWSVXHUwMDA0Qlx1MDAwMihB2DvU7qhJ6Jx0n/Od7+tX/vz0+fOa/9j21v75ec17KLn1Wrnj9te+2OP3XqdbazXxXHUwMDE0XHUwMDA0f3dbvU4puLLq++3uP3/7reF2bj2/XXdLnnNf6/bcetfvlWstp9Rq/FbzvUb33/bfI7fh/avdapT9jlx1MDAxM95k3SvX/Fbn+V5e3Wt4Tb+Lpf9cdTAwMDf//vz5z+DfiHXlmttoNcvB5cGJiHlGjlx1MDAxZT1qNVx1MDAwM1NccuGCXHUwMDAzIzC4oNbdxpv5Xlx1MDAxOc/eoMFeeMZcdTAwMWVa29y+K3b2XHUwMDFlL5t5wXP7f0C+d212w7ve1Or1vP9Yf65cdTAwMDe3VO11vPBs1++0br1CrexX8TxcdTAwMWQ5Pvhet4VVXHUwMDEwfqvT6lWqTa/bXHUwMDFk+k6r7ZZq/iNcdTAwMWVcdTAwMTNkcNBtVoJcIsIjXHUwMDBmwVx1MDAwNeBQJkBcdTAwMTCquDBE8cHp4PvUYYRqo1x1MDAxOFBcdTAwMGVajtq11apjO6Bdv5DgXHUwMDEzWnbtlm4raF6zXHUwMDFjXqOhZGjkmfuvT6uZwzlcdTAwMDPA21x1MDAxOGZcYojBJVWvVqn69lx1MDAxYVx1MDAwMEdcdTAwMTMutVx1MDAxMs+3ipjiXHUwMDA1TUJcdTAwMDVQ4FLqsEmtXHUwMDAx7f1y4Fx1MDAxYv9cdTAwMWSt0qrbab9U3VrX/lx1MDAxMTHe2r0z6lhR54o0u5bnXHUwMDFi5sdlsVrq7JOTk8vCuVuQg7KGPNH3XHUwMDFl/LXBib++pFx1MDAxNXt4uv90kXso+p1tb72a71x1MDAxNlx1MDAxNfCb8cW6nU6rP225XHUwMDE5mTv3Yoeu/jLtXHLDYl9+XHUwMDBim73XLrvPwUulJFxco1trqfXgfL3WvMWTzV69XHUwMDFlXHUwMDFla5Vuw3j/XHUwMDE0sTeGMkN2RiGG6NGjr1x1MDAxMCOAXHUwMDE50IqEXHUwMDBlO1x0YtJreVkhRqVBXGZFXHUwMDA0XHUwMDAywoFTot6PMX7HbXbbblx1MDAwN1x1MDAwM3dcZs6oyThcdTAwMDNxXFxB41xmXHUwMDEwakK754Yr8/TO0Fx1MDAwYlpNP1978oKyXHUwMDFjQTUnIFx0KGOIUENX5dxGrf441LCBXHUwMDFio+VcdTAwMWLt9q//iFZ110NcdTAwMTOCMsXQxVx1MDAxYvVaxfr5Wlx0XHUwMDFmyutcZoWAX8OUPbigUSuX61x1MDAxMX8soVx1MDAwNS6W2dmfJn22OrVKrenWz4dcZkxccsmOV/KffXJMXFxcbkWS4pIqIyhorvTUgVx0rdO9XHUwMDFi/fitcX2+sd+oXHUwMDFlVcVV/2TZXHUwMDAzkyrHXHUwMDAwZ1x1MDAxNORzYFx1MDAwZcUloO8oYFxu/3/O/dmFpVx1MDAxOFx1MDAxM4daOVx1MDAxOKacKVx1MDAxMYtHrlxyIZpcbj3/eEzLcGfyxtvtq1x1MDAxZtWzwl3ha+OSi527+vLm+cLvf9z3v/Ltw69cdTAwMWRR3n2EXHUwMDFl8O2EhDxTuVAo7+/lbkuHeoPT80b9eKd5WZlDuVx1MDAxOVVvRsWq+9pe7m6HNjo7V+Vt/1x1MDAxZe6P6/OoXHUwMDA1Tjpt2rwvXHUwMDFkXHUwMDE1clx1MDAwN0e6sF1oXHUwMDFk+lx1MDAwN+8qd1x1MDAxMo9cdTAwMWFfQVOae/2jsXFSv5dU+OubZVM5OGo9PE5n7rLwMy7V6NFBXHUwMDFlkIgykis2fVx1MDAxZUh3tyXNXHUwMDAzKK1S8lx1MDAwMNdcdTAwMGXD9jCv/Cy7PMDH8TFcdTAwMTbDf6VBWDKTgc7Lmo9cdTAwMDFcdTAwMTk6msK/8qWO5zWTKJhcdTAwMWG6fm5cdTAwMTRsXHUwMDAyi1x1MDAxOaVgXHUwMDAzXHUwMDFiU1x1MDAwM+858MdEnoakwFx1MDAwM+CCXHUwMDEzXHUwMDFkYdyT4i49i35I3FEyMfCMcDTlhqlxgYcs1GFcXFx1MDAwZlximMgs8IjDjSQmyrVcdTAwMDbxx7hjXHUwMDE0SCEhTsRcdTAwMDQzmnEuYfZAXGasW3Qgdn2342/WmuVas4Inw3yGwVjq2fuuXHUwMDEzhzAqXHUwMDA1R3RRXHUwMDAyQZGE1W5cdTAwMWbPbdtmQ1x1MDAxMSlQR1xuIVxyXHUwMDA3yU3kipfeyDRF83LxILGuec3yRKOIUehPXG6lXHUwMDFj/idUqExcdTAwMDZWgYMyLyDMQe9cdTAwMThlKsms8VFcdTAwMWUzq+52/a1Wo1HzsfpPWrWmP1rNQX1u2PCuem559Cw+VvTcKFx1MDAwZbRticOkO/ztc1x1MDAxOCnBXHUwMDFmg9//+2Xs1etxXHUwMDE3tp+I84YlfIr+fJN0pJSz0cOv0MUlw3yKXjo1dDVcdTAwMGVcbnDyXGJ721vffpQ2L2BcdTAwMDOKXHUwMDA172Ohi09CLkaZI1x1MDAxOJJcdTAwMDFuOOJTRIpcdTAwMDVfZ9rRRFx1MDAxM2OoXHUwMDA0TcjSSEfJXHUwMDA1YGTDgnuIycPW9UFLXlx1MDAxZJT13f3ZUfuGnlx1MDAxZPKfynFeyjGj6l2tYucvSCdcdMfxXHUwMDBmXHUwMDEyXHUwMDE2+4qzXHUwMDFmLfAoiYDEXGJaY1wipVxiUtN3wKe33rKCtU5cdTAwMDNrRVx1MDAxY8OxNZShXHUwMDAxWC+BwFx1MDAwM6Q7iohcdTAwMGbocDczeOP7XHUwMDA03lx1MDAxZdJcIq+zYIE3gWuMXG68gY3v4Epa0KTo41x1MDAxMt2NSja9zDvYOqnkjqhqyZvzvavTi2qp+vWDx78mhlx1MDAxZlx1MDAxMlBkQ1x1MDAwNFx1MDAwNZ4kmqlcYvlcYr7OiaMkRiVjWiFtJ9nJvFx1MDAxOclcdTAwMTJcYoPijrxB3L2HK12RnD686lx1MDAxNVx1MDAwZp425FaDbPbORK/4kyvNiytlVL0/i/2QvvvxXHUwMDBmMjNcdTAwMDWbJem9jYIpZUZcdTAwMGZcdTAwMGYmQVx1MDAxMKlcdTAwMTjRfHpcdTAwMGWW3nxLmlx1MDAwNFx1MDAxOE1LXHUwMDAyXG5cdTAwMWNtXGZRQFnWSWA6XHUwMDBlXHUwMDA2XHUwMDA0oVx1MDAxZqniR3SyL46D5Votf+FcdTAwMWNsXHUwMDAyh1x1MDAxOeVgXHUwMDAzXHUwMDFiUyMvuZNd8sTIk8LYvs7pIy9dZC7p6JamXHUwMDBlhlx1MDAxNuGGcEKJNEORx4hxJDOCaFx1MDAwM1ooynl2kWdQaFx1MDAxOabBSKBcXI2LQ2rn63BGXHUwMDE4wlx1MDAwMGgqqVx1MDAxOY1LKvCo5lGVtrA+9zfFZXKf+1x1MDAxNH3SYcqLdoZzQTUxQiCY2s68cETyc9hDLzmAeVx1MDAxZLJk7OWCyX3uQ0alq6Vho7CtXHUwMDEwuLlcdTAwMTRcdTAwMWFcdTAwMTRmu7hNXHUwMDE0XHUwMDFjXHRCXGKtJeVcdTAwMTRcZo/ZtFJcdTAwMWTuid5sP3E/XHUwMDBly/tcdTAwMTT9OTOcof8ndr1TJrF6XHKfYT5lOmVbTkCTTDvAqMK4tLOhR1x1MDAwMY1Sh1wi2+DUgLQ/Mlx1MDAwNDTJXHUwMDFkaWPMoFSkhof9SpFZ29QhdpYrXHUwMDA1jf9Hc81gNpfSXGZcdTAwMTBkVlx1MDAxZtBe0YA41FBlQYrhXHUwMDAzXHUwMDAzeiRcdTAwMGKD41x1MDAwNVxyXHUwMDE0Nlx1MDAxMuproGiMXHUwMDE2KP4jV7xlsG7SXHUwMDE4XCJxbIojXHUwMDFjMHtcYkFcdTAwMDVXMZO0g4RcdTAwMDBcdTAwMTDFlE2ETOskk8ZcdTAwMTOYlYazRFdcdTAwMGVOxpx4XmiGzZ9cdTAwMDRmKFx1MDAwNdArpJl+XHUwMDFjMX0u1pJOgUA1ZFeYIEkzXFxhK1xmz1xyt8OMhklCMckwxSC7rmnpXHUwMDE4XHUwMDE0XtyuMEF6xWSYZFwi5Fxmg1xiXHUwMDA1kUJYZaBRxsWwjGDcc6pM+IyrimVvJmdAQFAk2kZgXHUwMDE2UiQ+e8Jgi2rkb1x1MDAxYUOKMKNeqcGM3Kx0ly/+vv/Acr9fX5y6hzfXh+R4N8EmgsmQXCJhMVx1MDAwMVx1MDAxZFHAY0ZR7mhO0dHAXHUwMDAwIKc0sNJwtp7ozvZcdTAwMTN35Fx1MDAxOfEstcNcdTAwMWaISOzroUhJXHUwMDE4g1kmdqW385KiXHUwMDFh5n5cdTAwMDdcdTAwMTBcbjhcdTAwMTOgsFx1MDAxMUKkeJ5cdTAwMWVhXHUwMDFjhjrBKNQoQI1cdTAwMWGxa55cZi1EzLDHP0wpr7jFgGk0KFSkXHUwMDBi6ep/8vvC2z2R3fLvKt8/qNxvbVx1MDAxZW/+7OqfV1d/RtW7WsVmNU9/tWrhXHUwMDFk0/RcdTAwMTPKnTQyMf5BpjT3x7fqU1x1MDAxOfLFYt4tnpeqV4WN+lnCmo2ZXHUwMDFh7eBcXO+c9HKHXHUwMDA3Z113vXrQudl4qO9OV+5rXpwjXHUwMDBiS02xyatJVeJyXHUwMDA14NoopF7TS4Z0d1vW5FxuLC25XG6kcotJrmJMco0ssHxNrlx1MDAxMrOrnbKbQXbNeiSFyqGjKSMpW69jXHUwMDFjv35v2lx1MDAxM7Xyv77bjVx1MDAxN+qtyve1783xIyxcdTAwMDKGylx1MDAxOVxmoNS9m2Hnn2l8ZVx1MDAwMmNcdTAwMWNcdTAwMWRfmWj5O6gwJ4mDL+jAXHUwMDAwis6wvcTR3vXm7Teyv5477uzU1pv89NvjzrJcdTAwMDer5Kg17LwzrpRcclZcdTAwMThcblZhuGNQh2CsZlx1MDAxZKxcdTAwMTFNXHUwMDFlMuF4b6TQilx0XHUwMDEzXHUwMDFkgF1cYlx1MDAxNW7vNftcdTAwMTe3x9c7ZVLNre+vXHUwMDFmk3rx8CdcdTAwMTWeXHUwMDE3XHUwMDE1zqh6V6vYrKjwatXC/KlwRuZOYtjjb5g9XHUwMDEzTi13v3/WKPTFjr7eJFx1MDAxN/6RPKrRq4TJ7TOVu9voXHUwMDFll76dNd2LgtgvbNx5lav1/nTlLlxyc+cscVwirFCEK1x1MDAxMd1cdTAwMTllXHUwMDEyXHUwMDE5SHe3pSVcdTAwMDNcIoVcZkhiXHUwMDFjvlx1MDAxODKgx5CBMczdjklcdTAwMTi7XHUwMDA2/O/M3PeQXHUwMDEwP1lcdTAwMGVcXP/VXHUwMDFlfybBpbrb7XpdZMLXPd9vNbuLJvFcdTAwMTPIbmyi+lxmXHUwMDBm8Vx1MDAwZT4vk/eM4VRwXCJcdTAwMDUjU4dweX/f39l+2uHXrFuu7l+oRv/SXfZcdTAwMTBcdTAwMTZ2N1x1MDAwMCUkl1JcdTAwMDCG8PB4nZLS4YBS1273xIBnuJXTlHzeLmoxoFx1MDAxNzyJfbt597he8lxi9b8+Ulwi+24+d978SefnReczqt7VKjYrOr9atZBcdTAwMTWdX61aqJvNLX29u8W3pPTy51x1MDAwN1x1MDAwZpVcXG5KevxG9TH+Qf6P2Hx0ksdo11x1MDAxZWFMaVxyYno6n+5cdTAwMTfLylx1MDAwNVx1MDAwNEvjXHUwMDAyii6KXHUwMDBijKPzKsZcdTAwMDVcZppIuFAruKp0eja/XHUwMDE5MN1cYlx0/r524SHx/fL8173bqblNXHUwMDFmKXG3Vyrh0yXzejVcXPiceP1cdTAwMDTSO8rr3/Y472D4KD6TwtpQ6+UgZpiR97Tlnp6ap7PKZvGylds4h0b+dNmjWlx1MDAxOe1IXHUwMDAzxE5cdTAwMDWFmEhXUjh2wi5+1FIwfCCUMY46fcG7QbKvxW5+s974arbyxe5cdTAwMWb8x5Ms/tzTY25cdTAwMTQ/o+pdrWKzovirVVx1MDAwYllR/NWqhflT/IzMnaRcdTAwMWPG33BKa98xvvDy28crh2h/cmxcdTAwMDVcdTAwMTNcdTAwMDXgTNLplUN6+y0px9CEpXFcZkVcdTAwMTbFMaZTXHUwMDBlVFx1MDAxOY5qLjqz6v9DOlx1MDAxY7XGUG1cdTAwMGZcdTAwMDOrs2jdMIFKT6FcdTAwMWImPUtqMKeLXHUwMDA2XHUwMDAxyX1cdTAwMDFcdTAwMDJQz3NcdTAwMDPTr0ms9o+L9fouK/hf20y15GX+zr1e9ohmmjtcdTAwMTiqWmKYXHUwMDA0fVx1MDAwMWIopIWRjkBtpbnKXFw2jFm6M2ZgwFx1MDAxMIFBRt+yd+l7ZMN957Kfq2z+qKpK+3ZrY+Obq9tnP2XDvGRDRtW7WsVmJVx1MDAxYlarXHUwMDE2spJccqtSXHUwMDBik3j4+Fx1MDAxYi4hX1x1MDAxNjqRL2thqN1cdTAwMGV++uSaXs1Lm1xcTVpylYQuKrnqMcl1XGZfJswgX1ZvWFx1MDAwNrs6dDnvu8hgXyaNn37byZ/vXHUwMDFmXHUwMDFmfbF/jM4+uet5Xb+WOokmXHUwMDFi0jyBSca283/rXHUwMDEzpcZ14uJ3mVx1MDAxONacXHUwMDFhLYDNsJAlfcXQkoZ1sP0/U6hcdTAwMTCQjGL0XHUwMDBl78sqXHUwMDE4c4BJxlXmKlx1MDAxOIyDXHUwMDE1zijeXGLsmmFcdTAwMTVcdTAwMGZyyZ3xu0RyorRcdTAwMWHaROpvseR9luXlQjBit8IhWIfcaFx1MDAxYbnqdTtcIq00XHUwMDExxK7GJnY19stcdTAwMDVcdEveh1x1MDAxZmOVlp0ne5L9XGZ8KCznU/Tn7JtcdTAwMDFFXHUwMDEy/ygz0NpcbrxcdTAwMTmW16RPiV5SXGKRXHUwMDAwXHUwMDBlXHUwMDE3WNtCWM+iw3tcdTAwMDFcdCZcdTAwMWNcdEoxXHUwMDA1zDBcdTAwMDZmxK45QlxikY7dt9A6ONGM6DFcdTAwMTBcIrD57Yt77IxcdTAwMDBjKJD4XHUwMDBiRYDYxXJcdTAwMWM+4IVcIktcdTAwMDEmxOFKcFCSSqVcdTAwMTFRTPw1XHUwMDFm2lx1MDAwMWk4J4Tad3wgXHUwMDE3TMeSJJPS59eOmKSEXCJUMlwiqNAmvpdcdTAwMTF37I5mTHGBXHUwMDE2XHUwMDBiyVTMpFVcdTAwMDKxZFe2n7hcdTAwMTPPXHUwMDBizCRJ3jaDM/vuVD3DsED6QMmSoplcdTAwMTLSXHUwMDAxu1x1MDAxZj/XytjN3IZljlx1MDAwMcegrmBcdTAwMTK4nYmhR+ya42ZAxNFcdTAwMDKBk1x1MDAwMlD7hoKw2kM+JFx1MDAxY5RiRiq7ZT5nXHUwMDEw31ODW9/h5i1vjV1mLJtcdTAwMDE4wM7+4kiJhGG2VaPvSVx1MDAxYeyJSKyu5VxiL1x1MDAwNONN67ehWfrowDBbXHUwMDAzKkBSXHUwMDEwOliCQlh8IzTlIFx1MDAwMCuqXHUwMDA3RGU1gSzRi4OTo/47L1x1MDAxOItsZVx1MDAxN+NklGKiYDO8TTF9ouiyolx1MDAxOJNcdTAwMGV7fte1XHUwMDA0VFx1MDAwNsNbPVtcdTAwMTSzkk4pad/LrNiIXfNDMbxcdTAwMTFcYlRnRnOmkVSNQzHlgFx1MDAwNo2pXHUwMDA1qN2bKbZcdTAwMDSKoZnYZvxvRsimXHUwMDA2seC9YkhcdTAwMDFcdTAwMDBZqaaMKjlmL0dKXHUwMDFkgdyIXGIuUPNgXHUwMDEye+N2s+lzI0esYsq+XCJcdTAwMDVcdTAwMThcdTAwMTNKo9aKb2kmXHUwMDFkXGZuJIlcdTAwMDSI3Ss0btMqYdl6ojPbT8yNk8Ds08tcctbcdtv2dnmD1kC/rpVf+lx1MDAwMMOnXFy7r3n9zXjc/XJcdTAwMTN8bMdXUJ9cdTAwMTaJPPusf/716a//XHUwMDAxsk3fXHUwMDAxIn0= App()Screen()Header()Footer()Container( id="dialog")Horizontal( classes="buttons")Button( "Yes", variant="success")Button( "No", variant="error")Static( QUESTION, classes="questions")

Here's the output from this example:

ExampleApp ExampleApp Do you want to learn about Textual CSS? ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔  Yes  No  ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ^p palette

You may recognize some elements in the above screenshot, but it doesn't quite look like a dialog. This is because we haven't added a stylesheet.

CSS files

To add a stylesheet set the CSS_PATH classvar to a relative path:

What are TCSS files?

Textual CSS files are typically given the extension .tcss to differentiate them from browser CSS (.css).

dom4.py
from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal
from textual.widgets import Button, Footer, Header, Static

QUESTION = "Do you want to learn about Textual CSS?"


class ExampleApp(App):
    CSS_PATH = "dom4.tcss"

    def compose(self) -> ComposeResult:
        yield Header()
        yield Footer()
        yield Container(