Kivy's Complex Widgets

Learn How to Use Kivy's Complex UX Widgets in Your Apps
Heads up! You've already completed this tutorial.

Kivy is a powerful framework for developing multi-touch GUI applications using Python. It provides a set of rich built-in widgets which you can use to build complex GUI applications.

In a previous tutorial we covered the basic Kivy widgets such as text inputs, buttons and checkboxes. In this tutorial, we will take things further, exploring some more of the more complex widgets that Kivy provides. These include: Bubble, DropDown, FileChooser, Popup, Spinner, RecycleView, TabbedPanel, VideoPlayer, and VKeyboard. With them, you can add advanced features to your Kivy apps.

Writing an Outline Kivy App

We'll start this tutorial with a simple application skeleton, which we will then modify below. Save the following code in a file named app.py:

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout

class WidgetNameApp(App):
    title = "WidgetName Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout()

        return root

WidgetNameApp().run()

Here, we've created a Kivy application with an empty window. The BoxLayout acts as the root widget, this will act as the container to add our complex widgets to. The build() method sets the window's background color to a dark teal shade and adjusts the window size to 360x640 pixels, which is a mobile-friendly size.

To learn more about creating your first Kivy app, check out the Getting Started With Kivy for GUI Development tutorial.

Providing Option Selections With Spinner

The Spinner is a dropdown selector that allows users to choose one option from multiple choices. This is ideal when working with a list of simple text choices. Below is an example that builds a Spinner that lets you select from different parts of this website.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner

class SpinnerApp(App):
    title = "Spinner Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (300, 300)

        root = FloatLayout()

        # Create the Spinner
        spinner = Spinner(
            text="Home",
            values=("Home", "Latest", "FAQ", "Forum", "Contact", "About"),
            size_hint=(None, None),
            size=(200, 70),
            pos_hint={"center_x": 0.2, "center_y": 0.9},
            sync_height=True,
        )

        root.add_widget(spinner)

        return root

SpinnerApp().run()

The Spinner widget works as a simple dropdown list, allowing users to select one option from multiple text choices. We've set the Spinner to start with "Home" as the default text and provided other options ("Latest", "FAQ", "Forum", "Contact", and "About") as a list of values.

Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.

More info Get the book

You need to repeat the "Home" option in values so that you don't lose it when you select another option.

Run it! You'll get an app that looks as shown below.

A Kivy app showing a Spinner Widget A Kivy app showing a Spinner Widget

The dropdown spinner allows users to select from predefined choices. You can use this widget to create elements that work like dropdown list, optimizing space and providing a clean UI.

Providing Options With DropDown List

The DropDown widget provides a more complex menu component that allows users to choose from multiple options. Like spinner, this provides an intuitive way for users to select from a set of choices, but here you can display more than just text. This makes it more complex to use, but allows for more flexibility.

Below is an example of using the widget to create a dropdown list that lets you select your favorite GUI library, displayed on a series of Button objects.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown

class DropDownApp(App):
    title = "DropDown Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (200, 200)

        root = BoxLayout(orientation="vertical", padding=10, spacing=10)

        # Create a dropdown with 4 buttons
        dropdown = DropDown()
        for item in ["Kivy", "PyQt6", "PySide6", "Tkinter"]:
            option_btn = Button(text=item, size_hint_y=None, height=50, width=150)
            option_btn.bind(on_release=lambda btn: dropdown.select(btn.text))
            dropdown.add_widget(option_btn)

        # Create a main button to show the dropdown
        button = Button(
            text="Library",
            size_hint=(None, None),
            size=(150, 50),
        )
        button.bind(on_release=dropdown.open)
        dropdown.bind(
            on_select=lambda instance, text: setattr(button, "text", text),
        )
        root.add_widget(button)
        return root

DropDownApp().run()

In this example, we have a DropDown widget that lets the user to select a library from a list of options. You populate the dropdown with four options "Kivy", "PyQt6", "PySide6", and "Tkinter", which are displayed in Button objects.

We set each button to trigger the dropdown.select() method when clicked, passing the button's text as the selected value.

Then, we anchor the dropdown to a Library button. When we press the Library button, the dropdown menu opens, displaying the options. Once we select an option, the on_select event updates the main button's text to reflect the chosen library.

Run it! You'll get a window with a dropdown list in the lower left corner. Click in the dropdown widget to change the current selection.

A Kivy app showing a DropDown widget A Kivy app showing a DropDown widget

Accessing Files With FileChooser*

The filechooser module provides classes for describing, displaying and browsing file systems. In this module there are two ready-made widget views which present the file system as either a list, or as icons. The example below demonstrates these both in action.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.filechooser import FileChooserIconView, FileChooserListView

class FileChooserApp(App):
    title = "FileChooser Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout(orientation="vertical")

        # Create icon-view and list-view file choosers
        filechooser_icons = FileChooserIconView()
        filechooser_list = FileChooserListView()

        root.add_widget(filechooser_icons)
        root.add_widget(filechooser_list)

        return root

FileChooserApp().run()

In this example, we create file chooser widgets to browse and select files using two different views:

  1. Icon view (FileChooserIconView)
  2. List view (FileChooserListView)

The icon view displays files as wrapped rows of icons, clicking on a folder icon will navigate down into that folder. The list view presents them in a list-tree like format, where clicking on a folder will show files and folders nested under it.

Run it! On macOS, you'll see a window that looks something like the following. Try clicking on the folder icons and entries in the list view to see how navigation works in the two examples.

A Kivy app showing file chooser widgets A Kivy app showing file chooser widgets

Building Quick Dialogs With Popup

The Popup widget allows us to display modal dialogs with custom content, layouts and widgets. They can be used to show messages or ask for input. The following popup message displays a simple message, with a title, message and OK button.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup

class PopupApp(App):
    title = "Popup Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (400, 400)

        root = FloatLayout()

        button = Button(
            text="Open Popup",
            on_press=lambda x: self.show_popup(),
            size_hint=(None, None),
            size=(200, 50),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
        )

        root.add_widget(button)

        return root

    def show_popup(self):
        # Create and show the Popup
        popup = Popup(
            title="Info",
            size_hint=(0.6, 0.6),
            size=(300, 300),
            auto_dismiss=False,
        )

        layout = BoxLayout(orientation="vertical", spacing=10, padding=10)

        message = Label(text="Hello, World!")

        ok_button = Button(text="OK", size_hint=(None, None), size=(80, 40))
        ok_button.bind(on_release=popup.dismiss)

        layout.add_widget(message)
        layout.add_widget(ok_button)

        popup.content = layout
        popup.open()

PopupApp().run()

In this example, you create a Popup widget that displays information as a modal dialog. When the user clicks the Open Popup button, the show_popup() method is triggered, creating a Popup that occupies 60% of the screen in both directions.

We set auto_dismiss to False, which means the popup won't close if we click outside of it. The popup contains the Hello, World! message and an OK button. When we click the button, we dismiss (close) the popup. Popups are effective for displaying alerts, confirmations, or other information in a Kivy app.

Run it! You'll get a window with a button labeled "Open Popup". Click on the Open Popup button to display the popup window.

A Kivy app showing a popup dialog A Kivy app showing a popup dialog

Creating Contextual Popups With Bubble

The Bubble widget is a UI element commonly used for contextual popups, tooltips, or chat applications. Below is a quick Kivy application that shows some text and lets you click on it to change its format. We'll start by importing the necessary objects and subclassing the Bubble class:

python
from kivy.app import App
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.bubble import Bubble, BubbleButton, BubbleContent
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label

class FormattingBubble(Bubble):
    def __init__(self, target_text, **kwargs):
        super().__init__(**kwargs)

        # Customizing the bubble
        self.size_hint = (None, None)
        self.size = (dp(120), dp(50))
        self.arrow_pos = "top_mid"
        self.orientation = "horizontal"
        self.target_label = target_text

        # Add formatting buttons
        bold_btn = BubbleButton(text="Bold")
        italic_btn = BubbleButton(text="Italic")
        bold_btn.bind(on_release=lambda x: self.on_format("bold"))
        italic_btn.bind(on_release=lambda x: self.on_format("italic"))

        # Add the buttons to the bubble
        bubble_content = BubbleContent()
        bubble_content.add_widget(bold_btn)
        bubble_content.add_widget(italic_btn)

        self.add_widget(bubble_content)

    def on_format(self, format_type):
        if format_type == "bold":
            self.target_label.text = f"[b]{self.target_label.text}[/b]"
        elif format_type == "italic":
            self.target_label.text = f"[i]{self.target_label.text}[/i]"
        self.parent.remove_widget(self)

class BubbleApp(App):
    title = "Bubble Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = FloatLayout()

        self.text = Label(
            text="Click this text to apply formatting",
            size_hint=(0.8, 0.2),
            pos_hint={"center_x": 0.5, "center_y": 0.5},
            markup=True,
        )
        self.text.bind(on_touch_down=self.show_bubble)

        root.add_widget(self.text)
        root.bind(on_touch_down=self.dismiss_bubbles)

        return root

    def show_bubble(self, instance, touch):
        if instance.collide_point(*touch.pos):
            self.remove_all_bubbles()
            bubble = FormattingBubble(target_text=self.text)
            bubble.pos = (
                touch.x - bubble.width / 2, touch.y - bubble.height - dp(10)
            )
            self.root.add_widget(bubble)

    def dismiss_bubbles(self, instance, touch):
        if instance == self.root and not self.text.collide_point(*touch.pos):
            self.remove_all_bubbles()

    def remove_all_bubbles(self):
        for widget in self.root.children[:]:
            if isinstance(widget, FormattingBubble):
                self.root.remove_widget(widget)
                return

BubbleApp().run()

The FormattingBubble class inherits from Bubble and provides text formatting options for a label. It initializes with a specific size, arrow position, and horizontal layout. The bubble will contain two buttons: Bold and Italic. When pressed, these buttons apply the respective formatting to the target text by triggering the on_format() method. This method wraps the text in Kivy's markup tags [b]...[/b] for bold and [i]...[/i] for italic.

The BubbleApp class represents the Kivy application. It sets up a FloatLayout with a centered Label displaying a message. When the user taps the label, the show_bubble() method creates and positions a FormattingBubble above the tapped location.

The app also ensures that only one bubble is visible at a time by removing existing ones before showing a new one. Additionally, tapping outside the label dismisses any active bubbles using the dismiss_bubbles() method.

Run it! The app features a dark teal background and a mobile-friendly window size. The Bubble widget appears when we click the text.

A Kivy app showing a Bubble widget A Kivy app showing a Bubble widget

Displaying Data With RecycleView

The RecycleView widget efficiently displays data by recycling views or graphical elements. Instead of creating a widget for every item in the dataset, RecycleView reuses a small number of widgets to display visible items only, improving performance.

To illustrate, let's create a view that lets you inspect a database of employee profiles. The data is stored in a CSV file that looks like the following:

csv
name,job,department
John Smith,Developer,IT
Jane Doe,Designer,Graphics
Anne Frank,Artist,Painting
David Lee,Engineer,Civil
Ella Brown,Doctor,Medical
Frank Thomas,Chef,Culinary
Henry Ford,Driver,Transport
Nathan Young,Consultant,Business
Olivia King,Manager,Administration
Peter Wright,Director,Management
Queen Bell,President,Executive
Walter Thompson,Assistant,Support
Xena Garcia,Associate,Associate
Zack Harris,Consultant,Consulting

You can read and load this data with the csv module. To visualize the data, you can create a view with the RecycleView widget. For the individual views, you can use the Button widget, which will let you display the employee's profile:

python
import csv

from kivy.app import App
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview import RecycleView

class EmployeesView(RecycleView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.employees_data = self._read_from_csv()

        # Load the employees data into the data attribute
        self.data = [
            {
                "text": f"{employee['name']}",
                "on_release": self._create_callback(employee["name"]),
            }
            for employee in self.employees_data
        ]

        layout_manager = RecycleBoxLayout(
            default_size=(None, dp(56)),
            default_size_hint=(1, None),
            size_hint_y=None,
            orientation="vertical",
        )
        layout_manager.bind(minimum_height=layout_manager.setter("height"))

        self.add_widget(layout_manager)
        self.viewclass = "Button"

    def _create_callback(self, name):
        return lambda: self.on_button_click(name)

    def _read_from_csv(self):
        with open("employees.csv", mode="r") as file:
            return [row for row in csv.DictReader(file)]

    def on_button_click(self, name):
        popup = Popup(
            title=f"{name}'s Profile",
            size_hint=(0.8, 0.5),
            size=(300, 300),
            auto_dismiss=False,
        )
        employees_data = [
            employee for employee in self.employees_data if employee["name"] == name
        ]
        profile = "\n".join(
            [f"{key.capitalize()}: {value}" for key, value in employees_data[0].items()]
        )
        layout = BoxLayout(orientation="vertical", spacing=10, padding=10)
        message = Label(text=profile)
        ok_button = Button(text="OK", size_hint=(None, None))
        ok_button.bind(on_release=popup.dismiss)
        layout.add_widget(message)
        layout.add_widget(ok_button)
        popup.content = layout
        popup.open()

class RecycleViewApp(App):
    title = "RecycleView Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        return EmployeesView()

RecycleViewApp().run()

In this example, we subclass RecycleView to display the list of employees loaded from a CSV file. The _read_from_csv() method opens the file and reads the data using the csv.DictReader() class, which converts each CSV line into a dictionary whose keys come from the file header line.

The data attribute is key for the app to work because it'll hold the data that we want to display. To arrange widgets in a RecycleView, we use a RecycleBoxLayout. The viewclass attribute lets us set the widget that we'll use to display each data item.

It's important to note that for the RecycleView to work properly, we should set viewclass at the end when the data is already loaded and the layout is set up.

Then, we populate the RecycleView view with buttons, each displaying an employee's name. Clicking a button triggers _create_callback(), which generates a callback that opens a popup displaying the selected employee's profile details.

Run it! You'll get a nice-looking window listing the employees. Click a button to view the associated employee's profile. Scroll down to load more profiles.

A Kivy app showing a RecycleView Widget A Kivy app showing a RecycleView Widget

Building Tabbed UIs With TabbedPanel

The TabbedPanel widget lets us organize content into tabs, to improve navigation and optimize the use of space. This is commonly used in settings dialogs where there are lots of options available.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader

class TabbedPanelApp(App):
    title = "TabbedPanel Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        # Create the TabbedPanel
        root = TabbedPanel(do_default_tab=False)

        # Create the tabs
        general_tab = TabbedPanelHeader(text="General")
        general_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        general_content.add_widget(Label(text="General Settings", font_size=40))
        general_tab.content = general_content
        root.add_widget(general_tab)

        editor_tab = TabbedPanelHeader(text="Editor")
        editor_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        editor_content.add_widget(Label(text="Editor Settings", font_size=40))
        editor_tab.content = editor_content
        root.add_widget(editor_tab)

        profile_tab = TabbedPanelHeader(text="Profile")
        profile_content = BoxLayout(orientation="vertical", padding=10, spacing=10)
        profile_content.add_widget(Label(text="User Profile", font_size=40))
        profile_tab.content = profile_content
        root.add_widget(profile_tab)

        return root

TabbedPanelApp().run()

In this example, we create a Kivy app that shows a tabbed interface using the TabbedPanel widget. It disables the default tab and manually adds three tabs: General, Editor, and Profile, each represented by a TabbedPanelHeader object.

Inside the tabs, we place a BoxLayout to hold a label that displays a description as a placeholder tab content. Tabs allow us to organize content into visually distinct sections within an application's UI.

Run it! Your app will display three tabs. When you click the tab header, the app shows the tab's content. The active tab shows a light blue line at the bottom.

A Kivy app showing a TabbedPanel Widget A Kivy app showing a TabbedPanel Widget

Try and add some more widgets to each tab panel.

Allowing User Input With VKeyboard

The VKeyboard widget allows you to create a virtual keyboard that is useful for touchscreen applications that require the user to type in text. Below is a short app that demonstrates a virtual keyboard in action. When you type text using the keyboard, it is displayed on the label.

python
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.vkeyboard import VKeyboard

class VKeyboardApp(App):
    title = "VKeyboard Widget"

    def build(self):
        Window.clearcolor = (0, 0.31, 0.31, 1.0)
        Window.size = (360, 640)

        root = BoxLayout(orientation="vertical")

        self.display_label = Label(text="Type in!", font_size=40)
        root.add_widget(self.display_label)

        # Create the virtual keyboard
        keyboard = VKeyboard(size_hint=(1, 0.4))
        keyboard.bind(on_key_up=self.keyboard_on_key_up)
        root.add_widget(keyboard)

        return root

    def keyboard_on_key_up(self, *args):
        keycode = args[1]
        text = args[2]
        if keycode == "backspace":
            if (
                len(self.display_label.text) > 0
                and self.display_label.text != "Type in!"
            ):
                self.display_label.text = self.display_label.text[:-1]
                if self.display_label.text == "":
                    self.display_label.text = "Type in!"
        elif keycode == "spacebar":
            if self.display_label.text == "Type in!":
                self.display_label.text = " "
            else:
                self.display_label.text += " "
        elif keycode in {"enter", "shift", "alt", "ctrl", "escape", "tab", "capslock"}:
            pass
        else:
            if self.display_label.text == "Type in!":
                self.display_label.text = text
            else:
                self.display_label.text += text

VKeyboardApp().run()

In this example, we manually add a virtual keyboard to our app's interface using the VKeyboard widget and display typed text using a label.

When a key is released, the keyboard_on_key_up() method processes the input. Printable characters are appended to the label text. Backspace removes the last character, and the spacebar inserts a space.

You typically wouldn't use the VKeyboard widget as in the example above. Input widgets, like TextInput, will automatically bring up the virtual keyboard when focused on mobile devices.

We ignore special keys like Enter, Shift, Alt, Ctrl, and Escape. This allows us to interact with a virtual keyboard and see the input displayed dynamically in the label.

Run it! A virtual keyboard appears at the button of the app's window, allowing you to enter text in touch-based devices. When you type on the virtual keyboard at the bottom of the app's window, the label reflects what you've typed.

A Kivy app showing a VKeyboard Widget A Kivy app showing a VKeyboard Widget

Conclusion

Kivy provides a rich set of complex UX widgets that you can use to create cross-platform applications. Using the examples above as inspiration you should now be able to use Bubble, DropDown, FileChooser, Popup, Spinner, RecycleView, TabbedPanel, and VKeyboard in your own apps. See if you can extend these examples further, adding more widgets or functionality to them.

The complete guide to packaging Python GUI applications with PyInstaller.
[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Leo Well

Kivy's Complex Widgets was written by Leo Well with contributions from Martin Fitzpatrick .