Textual: The Definitive Guide - Part 1.
Tags: python, programming, textual, rich
Published: 2022-04-11 20:37:44

---
cover_image: https://raw.githubusercontent.com/Textualize/textual/main/imgs/textual.png
---

This is part one of my series on demystifying every damn aspect of Textual. In this article, we are going to explore the not-so-popular world of text-based user interfaces and how to create one using Textual. I’ve chosen to write about textual, partially because it has been used during my participation at the deepgram hackathon, but mostly because there isn’t any tutorial about textual yet in the wild world of the internet besides the readme file. 

If you have experience with text user interfaces in the past, you might come across other frameworks such as [urwid](http://urwid.org/), [curtsies](https://curtsies.readthedocs.io/en/latest/), [asciimatics](https://asciimatics.readthedocs.io/en/stable/intro.html), [prompt-toolkit](https://python-prompt-toolkit.readthedocs.io/en/master/index.html) to name a few. Nevertheless, If you have not, you are just fine because you are in the right place to learn about TUIs in general and using Textual specifically. I’ll show you how to develop a wordle clone step by step.

**Psst**: It's me from the future. I just wanted to let you know that when you see a `$` sign in front of a command that tells you to run it in the shell rather than the Python interpreter.

## 👉 Table Of Content (TOC). <a name="TOC"></a>

* [What is a TUI?](#TUI)
* [What is Textual?](#Textual)   
* [Install Textual](#InstallTextual)
  * [Dependency management with poetry](#DPMWP)
     * [What is Poetry](#WPoetry)
         * [Poetry Installation](#PoetryI)
     * [Python Versionning](#pythonv)
         * [Pyenv](#pyenv)
  * [virtual environments with poetry](#virtualenvironments)
  * [Starting a new Project](#StartingProject)
  * [Activating a virtual environment](#Activatingvirtual)
  * [Adding packages to your project](#Addingpackages)
* [Install Textual: revisited](#InstallTextualrevisited)
* [Basic Textual App](#BasicTextual)
* [User Interface](#UserInterface)
* [Textual Widgets](#TextualWidgets)
  * [Placeholder](#Placeholder)
  * [Button](#Button)
  * [Header](#Header)
  * [Footer](#Footer)
  * [ScrollView](#ScrollView)
  * [Static](#Static)
  * [Custom Widgets](#Customwidgets)
* [Reusable components](#Reusablecomponents)
* [Organize with views](#Organizewithviews)
  * [DockView](#DockView)
  * [GridView](#GridView)
  * [WindowView](#WindowView)
* [Widget Event Handler](#WidgetEvent)
* [Wrapping Up](#Wrapping)
* [Upcoming blogs on dev](#Upcoming)

## What is a TUI? <a name="TUI"></a>

Arguably, the most popular place to look up definitions is Wikipedia, and I quote:

> In computing, text-based user interfaces (TUI) (alternately terminal user interfaces, to reflect a dependence upon the properties of computer terminals and not just text), is a retronym describing a type of user interface (UI) common as an early form of human–computer interaction, before the advent of graphical user interfaces (GUIs).

In other words, a text user interface (TUI) is a type of user interface that relies on text rather than graphics to display information. And the most common use of TUI is in the form of a command-line interface.
The CLI of a TUI is generally not graphical, may have no mouse support, which is not the case of Textual, and may be designed for keyboard input. For example, the Linux cat command, btw I love cats, can be called on a TUI, in this case, your terminal, with the command `$ cat`.

This will bring up a text-based interface, where you can type text and then press enter to display back the typed text.

Now, having a high overview of TUIs, let's dive into the world of Textual.

## What is Textual? <a name="Textual"></a>

#### 🔝 [Go To TOC](#TOC). 

Textual is a relatively new text-based user interface toolkit. It allows programmers to build interactive yet sophisticated, user-friendly TUIs within the terminal. It is attractive to many programmers like me, and it is going to revolutionalize the world of terminal applications. What makes Textual attractive is the following core features:

* Textual helps you to build your terminal application with ease elegantly.
* Textual is the only way to create a highly interactive yet complex application within your terminal.
* Textual removes the horrible boilerplates of earlier TUIs by getting rid of decorators for handling events and such.

Regardless of what reasons you are going to use Textual, I’m glad you stumbled across this article. I will be going step by step through Textual basics and how to create a fully functional application inside your terminal. Each article I am going to publish from now on will dive into details about every element of Textual.
I’ve chosen to develop a wordle clone with textual, mainly because it has the right complexity level.

While I hope this article will appeal to a diverse range of programmers, I have a unique audience in mind as I write it. As with any job description, you don’t have to know every damn thing they mention, but it will help you to understand who I’m thinking about and how you might differ. My intended audience is:

* A beginner to intermediate programming skills using Python.
* Looking for some advanced Python concepts, which I hope you are eager to learn.
* Wants to learn about programming workflow, not just Textual.
* And, of course, has a good sense of humor.

Nevertheless, if you’re interested in creating a working application in Textual, you’re still in the right place! I’ll be showing you how to develop a wordle clone step by step. You’ll start with setting up a development environment with poetry and end up with an application running on your terminal. How exciting is that!


## Install Textual <a name="InstallTextual"></a>

#### 🔝 [Go To TOC](#TOC). 

It’s quite an unfortunate truth in the world of programming that the fun part has to come after a hustle. However, Getting Textual up and running is not a terribly complicated process, just by running one command `pip install textual` or by checking out the repo and running `poetry install`. Since I used the latter in `deepwordle`, let me give you a high overview of what `poetry` is and how it has been my go-to tool for building packages and managing dependencies. 

### Dependency management with poetry <a name="DPMWP"></a>

#### 🔝 [Go To TOC](#TOC). 

This section will highlight the most important things that will help you start working with poetry.
However, it is worth mentioning that there are many ways to install a python package: using pip, pypenv, pdm, poetry, and many more. Poetry is not only "yet another" python dependency management tool, but also it can be used to deliver packages. I just like this comic:

<figure>
  <img src="https://imgs.xkcd.com/comics/standards.png">
  <figcaption>Standards. Source: https://xkcd.com/927/.</figcaption>
</figure>

#### What is Poetry <a name="WPoetry"></a>

#### 🔝 [Go To TOC](#TOC). 

Poetry is a [pretty intuitive](https://python-poetry.org/docs/cli/) yet an elegant command-line tool for installing, managing dependencies, projects, and virtual environments, an all-in-one solution. The idea of inventing this tool is because of the different conventions of package management (using requirements.txt, MANIFEST.ini, setup.cfg, and many others) seemed to [the creator of Poetry](https://github.com/sdispater) not very convenient. Only one file should be required, namely `pyproject.toml`, which aims at being clear, readable, and part of the [PEP 517](https://peps.python.org/pep-0517/#source-trees) and [PEP 518](https://peps.python.org/pep-0518/#file-format) standard.

Whether you are new to dependency management or not, I recommend giving this tool a try. It is effortless, easy to use, and can simplify the maintenance and development of your python project.

##### Poetry installation <a name="PoetryI"></a>

#### 🔝 [Go To TOC](#TOC). 

To install this tool, you can follow along with the official [installation guide](https://python-poetry.org/docs/#installation). 

Essentially, If you are a Linux user, all you need to do is run the following command in your terminal.

```bash
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
```

#### python versionning <a name="pythonv"></a>

When it comes to python versioning, I recommend using `pyenv`. Therefore I am going to explain how to install a specific version of python interpreter with `pyenv`

##### pyenv <a name="pyenv"></a>

pyenv is a handy tool that allows you to install a specific version of the python interpreter into your machine with ease. Unlike the traditional way of going to the official python website and following the daunting steps to install a specific python interpreter, pyenv makes it easier by just running a simple command.

##### Pyenv installation <a name="Pyenvi"></a>

#### 🔝 [Go To TOC](#TOC). 

The [Readme file](https://github.com/pyenv/pyenv#installation) of the pyenv repository is fruitful with information about the installation process and how to use this tool.
However, I have compiled the essential parts to get this tool up and running on your machine if you are a Linux user.

Configure pyenv on zsh:

```bash
$ cat << EOF >> ~/.zshrc
# pyenv config
export PATH="${HOME}/.pyenv/bin:${PATH}"
export PYENV_ROOT="${HOME}/.pyenv"
eval "$(pyenv init -)"
EOF
```

Or if you are using the default bash shell, run the following command instead:


```bash
$ cat << EOF >> ~/.bashrc
# pyenv config
export PATH="${HOME}/.pyenv/bin:${PATH}"
export PYENV_ROOT="${HOME}/.pyenv"
eval "$(pyenv init -)"
EOF
```

Close your terminal and open a new shell session. Now, You can verify the installation process by running the following command:

```bash
$ pyenv --version 
```

##### Python versioning with pyenv <a name="Pythonversioning"></a>

#### 🔝 [Go To TOC](#TOC). 

You can issue the following command to install a specific python interpreter:

```
$ pyenv install <python_version>
```

For this tutorial, I am going to install python 3.10.1:

```bash
$ pyenv install 3.10.1
```

To list all available python versions you can install with pyenv, you can run the following command:

```
$ pyenv install --list | grep \ 3\.
```

To list all installed versions of python on your machine, you can run:

```bash
$ pyenv versions

  system
* 3.10.1 (set by PYENV_VERSION environment variable)
  3.8.10
  3.9.10
```

You can make it globally available by running:

```bash
$ pyenv global system 3.10.1
```

These are the essential commands for python versioning with `pyenv`. Now, let's go back to `poetry`.

### virtual environments with poetry <a name="virtualenvironments"></a>

#### 🔝 [Go To TOC](#TOC). 

Having the python executable in your PATH using `pyenv`, you can use it with poetry:

```bash
$ poetry env use 3.10.1
```

However, you are most likely to run into the following issue if you have virtualenv already installed using apt(if you didn't get this issue, that is great!):

```bash
Creating virtualenv deepwordle-dxc671ba-py3.10 in ~/.cache/pypoetry/virtualenvs

ModuleNotFoundError

No module named 'virtualenv.seed.via_app_data'

at <frozen importlib._bootstrap>:973 in _find_and_load_unlocked
```

To resolve it, you need to reinstall virtualenv through pip:

```bash
$ sudo apt remove --purge python3-virtualenv virtualenv
$ python3 -m pip install -U virtualenv
```

Now, you can tell poetry to use the pre-installed Python interpreter, 3.10.1 in this case:

```bash
$ poetry env use 3.10.1
Using virtualenv: ~/.cache/pypoetry/virtualenvs/deepwordle-dxc671ba-py3.10
```

Poetry has a proper path for setting up virtual environments(under `~/.cache/pypoetry/virtualenvs/`). However, if you want to let poetry create a virtual env called `.venv` inside your project directory, you can run the [following command](https://python-poetry.org/docs/configuration/#virtualenvsin-project):

```bash
$ poetry config virtualenvs.in-project true
```

You can take a look at different configurations of poetry by running:

```bash
$ poetry config --list
```

### Starting a new Project <a name="StartingProject"></a>

#### 🔝 [Go To TOC](#TOC). 

Having a virtual environment set up, you can use poetry to create a new project along with a virtual environment:

```bash
$ poetry new deepwordle && cd deeepwordle
```

This will equip you with the bare minimum to get things started.

```bash
deepwordle
├── pyproject.toml
├── README.rst
├── deepwordle
│   └── __init__.py
└── tests
    ├── __init__.py
    └── test_deepwordle.py
```

### Activating a virtual environment <a name="Activatingvirtual"></a>

#### 🔝 [Go To TOC](#TOC). 

If you are inside the project directory, you can activate a virtual environment by simply running:

```bash
$ poetry shell
```

or you can source the `virtualenv` path:

```bash
$ source ~/.cache/pypoetry/virtualenvs/<your_virtual_environment_name>/bin/activate
```

you can look up for previously installed virtual environments under `~/.cache/pypoetry/virtualenvs`:

```bash
$ ls ~/.cache/pypoetry/virtualenvs
```

To exit this virtual environment, use `exit`, `Ctrl-d`, `deactivate`, or your favorite way to nuke the shell.

### Adding packages to your project <a name="Addingpackages"></a>

#### 🔝 [Go To TOC](#TOC). 

Like the traditional way of using pip to install packages, poetry is capable of doing so by running the following command to import a package into your project:

```bash
$ poetry add <name_of_the_package_you_want_to_install>
```

This will add an entry under the `tool.poetry.dependencies` section:

```yaml
[tool.poetry.dependencies]
name_of_the_package_you_want_to_install = "^package_version"
```

You can always refer to [the official documentation](https://python-poetry.org/docs/basic-usage/#project-setup) for more detailed information about setting up projects with poetry.

I think this section is a good starting point for building a new project using poetry. In the follow-up sections, we are going to use this tool to install and manage Textual.

## Install Textual: revisited <a name="InstallTextualrevisited"></a>

#### 🔝 [Go To TOC](#TOC). 

As far as I can tell from the code base, Textual is currently available for ~~Linux os only~~ MacOS / Linux / Windows. To add textual to your project, run the following command:

```bash
$ poetry add textual
```

As you can tell, writing about dependency and environments setup is also frustrating for me. I don’t know what operating system you’re using; I hope a Linux flavor. Otherwise, I can’t predict how things might go wrong for you.

## Basic Textual App <a name="BasicTextual"></a>

#### 🔝 [Go To TOC](#TOC). 

Now everything is set up, let's create the most basic Textual app. To do so, create a file called `app.py` using your favorite code editor and add the following code:

```python
from textual.app import App

App.run()
```

This is the most basic Textual app you can ever code; Just importing the App class and then calls the run method class.

Run this code with python if you are still inside your `virtualenv` by running the following command:

```python
$ python app.py
```

or using `poetry`, but it is not required because we have already done `$ poetry shell` which means that you are inside the directory that contains the `pyproject.toml` file and the `virtualenv` is activated, still you can use:

```
$ poetry run python app.py
```

It will create a blank window with a black background within your terminal.

![Basic app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mb6msccu50hm1udz5v3.png) 

If a blank window with a black background is exactly the kind of application you were looking to write, then you’re done! Congratulations. Just kidding, ofcource it is not! right?

Now let's buid a slightly basic Textual app uisng the followin code:

```python
from textual.app import App

class MainApp(App):
    ...

MainApp.run()
```

This version uses inheritance to create a new subclass of App called MainApp. This is the application you’ll be developing throughout this series of article. You didn’t actually do anything woth the new class, so it still behaves like the previous basic version. However, we are going to do a lot in this and subsequent articles.

In the following sections, we will discuss the different components of Textual and how to use them for building our TUI app. But first, we need to design our app, aka mockup.

## User Interface <a name="UserInterface"></a>

#### 🔝 [Go To TOC](#TOC). 

Being good at designing UIs is an essential skill to possess as a front-end developer or an engineer. It gives quick insights into how to lay out visual application components before the coding phase. Let's apply this practice to our `deepwordle` app. Here are some capabilities I want to cover in this project:

* Render each word being guessed on the screen with spaces in between.
* From a basic wordle app standpoint, the word is only five letters long.
* Only six attempts are allowed for the user to guess the word.
* Display a nicely formatted message to tell the user what is happening.
* Tell the user the available keys for this game.

Given these features, it’s fairly easy to imagine the set of views and widgets for the application will require:

* A 6x5 grid view.
* A Button for each letter.
* Message panel.
* A header and footer.

## Textual Widgets <a name="TextualWidgets"></a>

#### 🔝 [Go To TOC](#TOC). 

In Textual, a Widget is the base visual component of TUI interfaces. It comes with a Canvas that can be used to draw on the terminal. It receives events and reacts to them. I find it convenient to think of a widget as a container with reactive attributes and behaviors, and it can contain other containers. The `Widget` class is the most basic such container.

Widgets come with twelve [reactive attributes](https://github.com/Textualize/textual/blob/f702d1ac23a22c5143d825ea46795fbf8ccdb12b/src/textual/widget.py#L80-L92) you can manipulate its visual properties such as the style of the widget, its border, padding, size and many more. Under the hood, A [reactive attribute](https://github.com/Textualize/textual/blob/f702d1ac23a22c5143d825ea46795fbf8ccdb12b/src/textual/reactive.py#L32) is implemented as a [python descriptor](https://docs.python.org/3/howto/descriptor.html).

Each reactive attribute has a separate watcher, which can be defined using the `watch` keyword followed by `_` and the name of the attribute to watch:

```python
  foo = Reactive("")
  def watch_foo(self, val):
    if val == "bar":
      do_something()
    #
    # custom logic
    #
```

When writing this article, Textual provides thirteen out-of-the-box types of widgets. We will discuss how to use the ones used in our project.

### Placeholder <a name="Placeholder"></a>

#### 🔝 [Go To TOC](#TOC). 

For prototyping purposes, a Placeholder can be used to see what the app looks like before the implementation phase. For example, our application looks like the following in terms of placeholders.

```python
from textual.app import App
from textual.widgets import Placeholder


class MainApp(App):

    async def on_mount(self) -> None:
        await self.view.dock(Placeholder(name="header"), edge="top", size=3)
        await self.view.dock(Placeholder(name="footer"), edge="bottom", size=3)
        await self.view.dock(Placeholder(name="stats"), edge="left", size=40)
        await self.view.dock(Placeholder(name="message"), edge="right", size=40)
        await self.view.dock(Placeholder(name="grid"), edge="top")

MainApp.run()
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o0vdzqlri07scq5bvolc.png">
  <figcaption>Deepwordle UI Design.</figcaption>
</figure> 

### Button <a name="Button"></a>

#### 🔝 [Go To TOC](#TOC). 

A button is a Label with associated events triggered when the button is clicked. A button has three properties:

* label: the text being rendered on the button.
* name: the name of the widget.
* style: label's style. It is defined using the 'foreground on background' notation. for example: style = "white on dark_blue"


```python
from textual.app import App
from textual.widgets import Button


class MainApp(App):

    async def on_mount(self) -> None:
        button1 = Button(label='Hello', name='button1')
        button2 = Button(label='world', name='button2', style='black on white')
        await self.view.dock(button1, button2, edge="left")

MainApp.run()
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr6w27a790ib8kfvqkw6.png">
  <figcaption>Vertical Buttons.</figcaption>
</figure> 

### Header <a name="Header"></a>

#### 🔝 [Go To TOC](#TOC). 

This widget defines a header for a terminal app. It can be used to display information such as the app's title, time, and icon(not customizable at the moment, but it has been requested in one of the issues/PRs)...

```python
from textual.app import App
from textual.widgets import Header


class MainApp(App):

    async def on_mount(self) -> None:
        header = Header(tall=False)
        await self.view.dock(header)

MainApp.run(title="DeepWordle")
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jscgujju2wsydav8sb5o.png">
  <figcaption>Header.</figcaption>
</figure> 

### Footer <a name="Footer"></a>

#### 🔝 [Go To TOC](#TOC). 

This widget defines a footer for a terminal app, and it can be used to display the available keys for the user.

```python
from textual.app import App
from textual.widgets import Footer


class MainApp(App):

    async def on_load(self) -> None:
        """Bind keys here."""
        await self.bind("q", "quit", "Quit")
        await self.bind("t", "tweet", "Tweet")
        await self.bind("r", "None", "Record")

    async def on_mount(self) -> None:
        footer = Footer()
        await self.view.dock(footer, edge="bottom")

MainApp.run(title="DeepWordle")
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8tu1oqkvkacfvsjzvhas.png">
  <figcaption>Footer.</figcaption>
</figure> 

### ScrollView <a name="ScrollView"></a>

#### 🔝 [Go To TOC](#TOC). 

```python
from textual.app import App
from textual.widgets import ScrollView, Button


class MainApp(App):


    async def on_mount(self) -> None:
        scroll_view = ScrollView(contents= Button(label='button'), auto_width=True)
        await self.view.dock(scroll_view)

MainApp.run()
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3a0uqtlokau6t97p4iui.png">
  <figcaption>ScrollView.</figcaption>
</figure> 
 
### Static <a name="Static"></a>

#### 🔝 [Go To TOC](#TOC).

```python
from textual.app import App
from textual.widgets import Static, Button


class MainApp(App):


    async def on_mount(self) -> None:
        static = Static(renderable= Button(label='button'), name='')
        await self.view.dock(static)

MainApp.run()
```

You can play with other widgets such as `TreeClick`, `TreeControl`, `TreeNode`, `NodeID`, `ButtonPressed`, `DirectoryTree`, `FileClick`.

### Custom Widgets <a name="Customwidgets"></a>

#### 🔝 [Go To TOC](#TOC).

By extending the generic widget class, you can create any sort of widget you want.

```python
from textual.app import App
from textual.widget import Widget
from textual.reactive import Reactive
from rich.console import RenderableType
from rich.padding import Padding
from rich.align import Align
from rich.text import Text

class Letter(Widget):

    label = Reactive("")

    def render(self) -> RenderableType:
        return Padding(
            Align.center(Text(text=self.label), vertical="middle"),
            (0, 1),
            style="white on rgb(51,51,51)",
        )

class MainApp(App):

    async def on_mount(self) -> None:
        letter = Letter()
        letter.label = "A"
        await self.view.dock(letter)

MainApp.run(title="DeepWordle")
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gc7mvp4llz5q52c3ua6.png">
  <figcaption>A Custom Widget.</figcaption>
</figure> 

It's just a simple widget Class declaring your custom Letter component, with custom rendering.

For more information about Widgets, besides this article, the only place to look for is [the Readme file](https://github.com/Textualize/textual#widgets) and the code base.

## Reusable components <a name="Reusablecomponents"></a>

#### 🔝 [Go To TOC](#TOC).

When developing web applications or any sort of apps in general, you tend to reuse existing code in your project. And to make your code reusable, the best practice is to create each component of the app in a separate file. This way, your codebase will look much more organized and structured.

```bash
├── deepwordle
│             ├── __init__.py
│             ├── app.py
│             ├── components
│             │             ├── __init__.py
│             │             ├── constants.py
│             │             ├── letter.py
│             │             ├── letters_grid.py
│             │             ├── message.py
│             │             ├── rich_text.py
│             │             └── utils.py
```

## Organize with views <a name="Organizewithviews"></a>

#### 🔝 [Go To TOC](#TOC).

Widgets in Textual are organized within a view that uses a docking technique to arrange them on the terminal. Docking is similar to the css grid layout, and it can be customized. 

By default, a widget will be rendered on [the center](https://github.com/Textualize/textual/blob/f702d1ac23a22c5143d825ea46795fbf8ccdb12b/src/textual/widget.py#L257-L259) of the terminal. In textual, you can dock or arrange widgets to the terminal's left, right, top, and bottom sides by changing the `edge` argument to `left`, `right`, `top`, `bottom` respectively.

In Textual, there are five types of views:

### DockView <a name="DockView"></a>

#### 🔝 [Go To TOC](#TOC).

It is the default view used by a textual app. It groups widgets either vertically(default) or horizontally to fill up all the terminal space. The `edge` argument can be used to control how the widget can be grouped.

By default `edge = top`.

```python
from textual.app import App
from textual.widgets import Placeholder
from textual.views import DockView


class SimpleApp(App):

    async def on_mount(self) -> None:
        view: DockView = await self.push_view(DockView())
        await view.dock(Placeholder(), Placeholder(), Placeholder())

SimpleApp.run()
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qppwj9eeisahmwp7jpo.png">
  <figcaption>Horizontal orientation.</figcaption>
</figure>

```python
from textual.app import App
from textual.widgets import Placeholder
from textual.views import DockView


class SimpleApp(App):

    async def on_mount(self) -> None:
        view: DockView = await self.push_view(DockView())
        await view.dock(Placeholder(), Placeholder(), Placeholder(), edge='left')

SimpleApp.run()
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ik91xm8od8e99mw2pbwi.png">
  <figcaption>Vertical orientation.</figcaption>
</figure> 

You can also control the size of each widget in terms of characters.

```python
from textual.app import App
from textual.widgets import Placeholder
from textual.views import DockView


class SimpleApp(App):

    async def on_mount(self) -> None:
        view: DockView = await self.push_view(DockView())
        await view.dock(Placeholder(), Placeholder(), Placeholder(), size=10)

SimpleApp.run()
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q394ivikv3is2d7kozkd.png">
  <figcaption>Horizontal orientation with fixed size.</figcaption>
</figure> 

As you can see, each widget(Placeholder) has a height of 10 characters.

### GridView <a name="GridView"></a>

#### 🔝 [Go To TOC](#TOC).

GridView is used to layout TUIs widgets in a rectangular/tabular manner by specifying the number of rows and columns, and a list of widgets to lay them out on the terminal.

Example of an empty grid:

```python
from textual.app import App
from textual.widgets import Placeholder
from textual.views import GridView


class SimpleApp(App):

    async def on_mount(self) -> None:
        await self.view.dock(GridView(), size=10)
        await self.view.dock(Placeholder(name='sad'), size=10)
        await self.view.dock(GridView(), size=10)


SimpleApp.run(log="textual.log")
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aly3fivjag5nnc36ez6x.png">
  <figcaption>Empty Gridview.</figcaption>
</figure>

Example of a 6x6 placeholders' grid:

```python
from textual.app import App
from textual import events
from textual.widgets import Placeholder


class GridView(App):
    async def on_mount(self, event: events.Mount) -> None:
        """Create a grid with auto-arranging cells."""

        grid = await self.view.dock_grid()

        grid.add_column("col", repeat=6, size=7)
        grid.add_row("row",  repeat=6, size=7)
        grid.set_align("stretch", "center")

        placeholders = [Placeholder() for _ in range(36)]
        grid.place(*placeholders)


GridView.run(title="Grid View", log="textual.log")
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ckr4sd7o97dz9fasjhb.png">
  <figcaption>6x6 placeholders' grid</figcaption>
</figure>

### WindowView <a name="WindowView"></a>

#### 🔝 [Go To TOC](#TOC).

A placeholder for widget.

```python
from textual.app import App
from textual.widgets import Placeholder
from textual.views import WindowView


class SimpleApp(App):

    async def on_mount(self) -> None:
        await self.view.dock(WindowView(widget=Placeholder(name='sad')), size=10)
        await self.view.dock(WindowView(widget=Placeholder(name='sad')), size=10)
        await self.view.dock(Placeholder(name='sad'), size=10)

SimpleApp.run(log="textual.log")
```

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofru48e1s7f5qrnzielk.png">
  <figcaption>WindowView</figcaption>
</figure>

## Widget Event Handler <a name="WidgetEvent"></a>

#### 🔝 [Go To TOC](#TOC).

In Textual, you can assign handlers for a widget using the underscore naming convention, unlike the traditional way of decorating a handler. For example, a key event handler can be simply written as:

```python
def on_key(self, event):
  ...
```

Instead of the usual way of capturing events like the following:

```python
@on(event)
def key(self):
```

Using the underscore convention, your code looks more readable and contains less boilerplate code. However, as a python developer, you might argue that writing boilerplate looks more Pythonic than the so-called "best practices", and I would agree with that. For instance, at first, I wasn't aware of that underscore notation and what was going on under the hood, and how in the world an event is being fired and handled until I went into the source code, and everything made sense to me. I actually like that notation, and it is more readable.

## Wrapping Up <a name="Wrapping"></a>

#### 🔝 [Go To TOC](#TOC).

Building your own TUI-based app allows you to take your UI skills to the next level. By using Textual, you can build any sort of terminal application you want. It can save you a lot of time and give your audience a better experience.

The Textual package hides many low-level details, allowing you to focus on the logic of your app.

In this article, you learned how to:

* Install and use Poetry.
* Install and use pyenv.
* Install and build custom interfaces with Textual.

You are free to use the code in this article as a starting point for various needs. Don’t forget to take a look at [the readme file](https://github.com/Textualize/textual) and use your imagination to make more complex apps that are meaningful to your use case.

## Upcoming blogs on dev <a name="Upcoming"></a>

#### 🔝 [Go To TOC](#TOC).

I am currently planning to share my experience on this platform that is made for developers. I joined Dev.to a few weeks ago, and as you can see from my profile on medium(I will be writing why I left medium in a separate article.), I mainly publish technical blogs on data science and computer vision with Python. I think this is just the start of me regularly publishing here, so let's grow together!

So, what's the catch? Well, there isn't one. I'm just giving this article away. This is a gift to you, and you can share it with whomever you like or use it in any way that would be beneficial to your personal and professional development. Thank you in advance for your ultimate support!

Happy Coding, folks; see you in the next one.