Kivy is an open-source Python framework that lets you write one application and run it on Windows, macOS, Linux, Android, and iOS. Built on OpenGL ES 2, it ships with native multitouch support and over 20 extensible widgets, making it one of the few Python tools that genuinely targets mobile platforms alongside desktop.
Python has no shortage of GUI frameworks. Tkinter ships with the standard library, PyQt and PySide wrap the powerful Qt toolkit, and wxPython offers native-looking interfaces. But when the requirement is a single Python codebase that can be packaged into an Android APK, an iOS app, and a desktop executable, the list narrows dramatically. Kivy has occupied that cross-platform space for over a decade, and with version 2.3.1 currently stable and version 3.0.0 in active development, it remains one of the more practical options for Python developers who want to reach mobile users without leaving the language they know.
What Is Kivy and Why Does It Matter
Kivy is a Python framework built from the ground up for developing graphical applications with innovative user interfaces. Unlike Tkinter or PyQt, which wrap native OS widgets, Kivy renders everything through its own OpenGL ES 2 graphics engine. This means your application looks identical on every platform, whether it is running on a Linux laptop or an Android tablet.
The framework was created as an evolution of the PyMT project and received a grant from the Python Software Foundation in 2012 to support the port to Python 3. Since then, the Kivy organization has expanded into a full ecosystem of tools: Buildozer for packaging, python-for-android and kivy-ios for mobile toolchains, Plyer for platform-independent hardware access, and KivyMD for Material Design widgets.
What makes Kivy stand apart from other Python GUI frameworks is its focus on touch input. Every widget in the library is built with multitouch support from the start. That design decision makes it a natural fit for building interfaces on phones and tablets, but it works equally well with mouse and keyboard on desktop platforms.
Kivy is licensed under the MIT License, which means you can use it freely in both open-source and proprietary applications. You can even make proprietary modifications to the framework itself without restrictions.
Installing Kivy and Writing Your First App
Kivy installs through pip like any other Python package. The current stable release is version 2.3.1, which supports Python 3.8 through 3.13. Windows 32-bit and Python 3.7 support were dropped starting with the 2.3.0 release.
# Install the latest stable version of Kivy
pip install kivy
# If you also want the example applications
pip install kivy_examples
Once installed, a minimal Kivy application takes just a handful of lines. The pattern is straightforward: you subclass App, override the build method to return the root widget, and call run() on an instance of your app class.
from kivy.app import App
from kivy.uix.button import Button
class HelloApp(App):
def build(self):
return Button(text="Hello from Kivy")
if __name__ == "__main__":
HelloApp().run()
Running this script opens a window containing a single button. Click the button and it visually responds with a press animation. That single file, without any modifications, can be packaged for Android, iOS, or desktop. That is the core promise of Kivy.
If you want to test with the latest development features, Kivy publishes nightly wheels. Install them with: pip install kivy --pre --no-deps --index-url https://kivy.org/downloads/simple/
Understanding the Kv Language
One of Kivy's distinguishing features is the Kv language, a domain-specific language for declaring user interfaces. Kv files separate your layout and styling from your Python logic, similar to how HTML separates structure from behavior in web development. By convention, a Kv file is named after your App subclass, minus the "App" suffix and in lowercase.
# File: hello.kv
# This file is automatically loaded by HelloApp
BoxLayout:
orientation: "vertical"
padding: 20
spacing: 10
Label:
text: "Welcome to Kivy"
font_size: 32
bold: True
Button:
text: "Click Me"
on_press: print("Button pressed!")
TextInput:
hint_text: "Type something here..."
multiline: False
The corresponding Python file becomes much simpler when you use Kv for the interface definition:
from kivy.app import App
class HelloApp(App):
pass
if __name__ == "__main__":
HelloApp().run()
Kivy automatically discovers and loads the hello.kv file based on the class name HelloApp. You can also load Kv strings directly in Python using Builder.load_string() or load specific files with Builder.load_file(), which gives you more flexibility in how you structure your project.
The Kv language supports property binding out of the box. When you write text: root.some_property, the widget automatically updates whenever that property changes. This reactive behavior eliminates the need to manually wire up callbacks for simple data-to-display synchronization.
Working with Widgets and Layouts
Kivy provides a rich set of built-in widgets and layout managers. Understanding layouts is essential because they control how child widgets are positioned and sized within the application window.
The key layout classes include BoxLayout for arranging children in a horizontal or vertical line, GridLayout for grid-based positioning, FloatLayout for absolute or relative positioning, StackLayout for flowing widgets that wrap when they reach the edge, and AnchorLayout for anchoring a child to a specific position within the layout.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
class LoginScreen(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.padding = 40
self.spacing = 15
self.add_widget(Label(
text="Sign In",
font_size=28,
size_hint_y=None,
height=50
))
self.username = TextInput(
hint_text="Username",
multiline=False,
size_hint_y=None,
height=44
)
self.add_widget(self.username)
self.password = TextInput(
hint_text="Password",
password=True,
multiline=False,
size_hint_y=None,
height=44
)
self.add_widget(self.password)
login_btn = Button(
text="Log In",
size_hint_y=None,
height=50
)
login_btn.bind(on_press=self.handle_login)
self.add_widget(login_btn)
def handle_login(self, instance):
user = self.username.text
pw = self.password.text
print(f"Attempting login for: {user}")
class LoginApp(App):
def build(self):
return LoginScreen()
if __name__ == "__main__":
LoginApp().run()
Two sizing concepts are central to Kivy layouts. The size_hint property accepts values between 0 and 1 representing proportional sizing relative to the parent, while size_hint_x and size_hint_y control individual axes. Setting size_hint_y to None and providing an explicit height gives you fixed-size widgets, which is common for buttons and text inputs.
Handling Events and Properties
Kivy's event system is built on a property model that is central to how the framework operates. Kivy properties are not regular Python attributes. They are descriptors defined at the class level that provide automatic event dispatching when their values change.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.properties import NumericProperty
class TemperatureConverter(BoxLayout):
celsius = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = "vertical"
self.padding = 30
self.spacing = 20
self.label = Label(
text="0.0 C = 32.0 F",
font_size=24
)
self.add_widget(self.label)
slider = Slider(
min=-40,
max=100,
value=0,
step=0.5
)
slider.bind(value=self.on_slider_change)
self.add_widget(slider)
def on_slider_change(self, instance, value):
self.celsius = value
def on_celsius(self, instance, value):
fahrenheit = (value * 9 / 5) + 32
self.label.text = f"{value:.1f} C = {fahrenheit:.1f} F"
class ConverterApp(App):
def build(self):
return TemperatureConverter()
if __name__ == "__main__":
ConverterApp().run()
The property types available in Kivy cover a wide range of data: StringProperty, NumericProperty, BooleanProperty, ListProperty, DictProperty, ObjectProperty, and OptionProperty among others. Each one dispatches an event automatically when its value is modified, and you can observe changes by defining a method named on_<property_name> in your class.
Touch events in Kivy propagate through the widget tree. When a user touches the screen or clicks the mouse, the on_touch_down, on_touch_move, and on_touch_up methods are called on each widget. The touch object carries information about position, whether the touch is a double-tap, and which button was pressed on a mouse. Widgets should call self.collide_point() to verify the touch is within their bounds before handling it.
Always define Kivy properties at the class level, not inside __init__. Writing self.celsius = NumericProperty(0) inside __init__ creates a regular attribute instead of a reactive Kivy property, and automatic event dispatching will not work.
Packaging for Mobile with Buildozer
Getting your Kivy app onto a phone is where Buildozer comes in. Buildozer is a command-line tool that automates the entire process of downloading the Android SDK, NDK, and all required dependencies, compiling your Python code, and producing an APK or AAB file ready for installation or upload to the Google Play Store.
# Install Buildozer
pip install buildozer
# Initialize a new project (creates buildozer.spec)
buildozer init
# Build a debug APK and deploy to a connected device
buildozer android debug deploy run
The buildozer init command generates a buildozer.spec file in your project directory. This configuration file controls everything about your build, including the app title, package name, version, required Python packages, Android permissions, target API level, and supported architectures.
Buildozer currently runs only on Linux and macOS. Windows users need to use the Windows Subsystem for Linux (WSL) or a Docker container. The Kivy team provides an official Docker image at kivy/buildozer:latest that simplifies this process.
A few critical lines in the buildozer.spec file deserve attention. The requirements line lists every Python package your app needs beyond Kivy itself. The android.permissions line declares which device permissions your app requires, such as INTERNET, CAMERA, or READ_EXTERNAL_STORAGE. The android.api setting targets a specific Android API level, which Google Play requires to be reasonably recent.
# Key lines in buildozer.spec
# App metadata
title = My Kivy App
package.name = mykivyapp
package.domain = com.example
# Python dependencies
requirements = python3, kivy==2.3.1, pillow, requests
# Android permissions
android.permissions = INTERNET, ACCESS_NETWORK_STATE
# Target the latest Android API
android.api = 34
# Minimum supported Android version
android.minapi = 24
# Supported architectures
android.archs = arm64-v8a, armeabi-v7a
For iOS deployment, the Kivy ecosystem provides a separate toolchain called kivy-ios, which compiles the necessary libraries and helps generate Xcode projects. The iOS packaging workflow requires a Mac with Xcode installed and an active Apple Developer account.
The Kivy Ecosystem: KivyMD, Plyer, and Beyond
The core Kivy framework provides functional widgets, but they follow Kivy's own visual style rather than the platform conventions users expect on Android or iOS. This is where the surrounding ecosystem fills the gap.
KivyMD is a library of Material Design widgets built on top of Kivy. It provides components like toolbars, navigation drawers, cards, date pickers, bottom sheets, and snackbars that follow Google's Material Design guidelines. If you are building an Android application and want it to look familiar to users, KivyMD is practically essential.
# A simple KivyMD app with a toolbar and button
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.toolbar import MDTopAppBar
class ExampleApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "Blue"
self.theme_cls.theme_style = "Light"
screen = MDScreen()
toolbar = MDTopAppBar(
title="My KivyMD App",
pos_hint={"top": 1}
)
screen.add_widget(toolbar)
button = MDRaisedButton(
text="Material Button",
pos_hint={"center_x": 0.5, "center_y": 0.5}
)
screen.add_widget(button)
return screen
ExampleApp().run()
Plyer is a platform-independent API that gives your Kivy app access to hardware features. Through Plyer, you can access the device camera, GPS, accelerometer, gyroscope, text-to-speech, notifications, battery status, and more, all through a unified Python API that works across platforms. Plyer handles the platform-specific implementations behind the scenes.
Kivy Garden is a collection of community-maintained widgets and extensions. These range from graph plotting and joystick controls to map views and circular progress bars. Garden packages are installed separately and extend Kivy's capabilities beyond what the core framework provides.
Other ecosystem tools include PyJNIus for direct access to the Java/Android API from Python, Pyobjus for accessing the Objective-C/iOS API, KivEnt as an entity-based game engine, and OSCPy for Open Sound Control network communication.
When to Choose Kivy Over Other Frameworks
Kivy is not the right choice for every project, and understanding where it excels helps you make an informed decision.
Choose Kivy when your primary goal is deploying a Python application to Android or iOS. No other mature Python framework gives you a real path to mobile with a single codebase. Kivy is also a strong choice for touch-centric interfaces, multitouch applications, kiosk displays, educational tools, and rapid prototyping where cross-platform reach matters more than a pixel-perfect native appearance.
Consider alternatives when you need your desktop application to look and behave exactly like a native app. Kivy renders its own widgets, so the visual style does not match the platform's native look and feel. For native desktop appearance, PySide6 or PyQt6 are stronger options. BeeWare's Toga toolkit is another contender if native platform widgets are a hard requirement and mobile deployment is also needed.
"The aim is to allow for quick and easy interaction design and rapid prototyping whilst making your code reusable and deployable." — Kivy Project Documentation
The performance characteristics of Kivy are worth noting as well. Because it uses OpenGL ES 2 for rendering, graphics-heavy applications and games can perform well. The framework is written in Python and Cython, with the performance-critical rendering pipeline compiled to C. For applications with complex UIs or animations, Kivy often delivers smoother results than widget-wrapper frameworks.
Looking ahead, the Kivy team has stated that version 2.3.x will be the final series in the 2.x line. Work on Kivy 3.0.0 is underway, with plans to remove deprecated features from the 1.x and 2.x eras and modernize the codebase. Community discussions have highlighted priorities like improved documentation, better beginner onboarding, and reducing accumulated technical debt.
Key Takeaways
- True cross-platform from one codebase: Kivy is one of the few Python frameworks that genuinely targets Windows, macOS, Linux, Android, and iOS from a single set of source files. Its OpenGL ES 2 rendering engine ensures consistent visuals across every platform.
- The Kv language separates UI from logic: The domain-specific Kv language provides declarative UI definitions with built-in property binding, keeping your Python files focused on application logic while the interface is defined in clean, readable layout files.
- Kivy properties power the reactive model: Defining properties at the class level using types like
NumericPropertyandStringPropertygives you automatic event dispatching and observer methods, which is the foundation of responsive Kivy applications. - Buildozer handles mobile packaging: The Buildozer tool automates the entire Android build pipeline, from SDK and NDK setup to APK generation. Combined with kivy-ios for Apple devices, it provides a practical path from Python script to app store.
- The ecosystem extends the core framework: KivyMD adds Material Design components, Plyer provides platform hardware access, and PyJNIus and Pyobjus bridge into native platform APIs. These companion libraries fill the gaps that the core framework intentionally leaves open.
Kivy occupies a unique position in the Python ecosystem. It is not trying to compete with Qt for native desktop polish or with Flutter for mobile performance. Instead, it offers Python developers a genuine path to mobile and touch-based applications without requiring a second language or a fundamentally different development workflow. For projects where that flexibility matters, Kivy remains a practical and actively maintained choice.