Kivy

Multi-touch capable cross-platform GUI framework. Provides consistent UI experience across mobile, desktop, and web through custom rendering engine. Specialized for game and interactive application development.

DesktopCross-platformPythonGUIMobileTouch

GitHub Overview

kivy/kivy

Open source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS

Stars18,475
Watchers603
Forks3,128
Created:November 3, 2010
Language:Python
License:MIT License

Topics

androidappioskivylinuxmacospythonuiwindows

Star History

kivy/kivy Star History
Data as of: 7/16/2025, 08:43 AM

Framework

Kivy

Overview

Kivy is an open-source cross-platform UI framework written in Python that runs on Windows, Linux, macOS, Android, and iOS. It enables development of touch-enabled graphical applications with natural multi-touch interactions and rich UI widgets.

Details

Kivy continues to evolve as a strong choice for Python cross-platform development in 2025. It particularly excels in touch-critical desktop applications, kiosk applications, and educational software where multi-touch interaction is essential.

The OpenGL-based rendering engine provides high-performance graphics drawing, while the unique KV language offers intuitive layout description. Kivy provides comprehensive features needed for modern UI application development, including rich widget libraries, animation capabilities, multi-touch gesture recognition, and seamless cross-platform deployment.

Integration with packaging tools like PyInstaller, Buildozer, and p4a makes creating executables and app packages for each platform straightforward. It's adopted across various fields including game development, educational software, industrial control panels, and digital signage applications.

Pros and Cons

Pros

  • True Cross-platform: Supports both desktop and mobile platforms
  • Touch-first Design: Natural multi-touch and gesture interactions
  • OpenGL Rendering: High-speed graphics drawing and animations
  • KV Language: Intuitive and maintainable layout description
  • Rich Widgets: Comprehensive UI component library
  • Python Productivity: Python's ease of learning and rich ecosystem
  • Open Source: MIT license allows commercial use
  • Active Community: Continuous development and support

Cons

  • Non-native Look: Does not use platform-specific native appearance
  • Learning Curve: Requires understanding Kivy-specific concepts and KV language
  • App Size: OpenGL dependencies increase binary size
  • Memory Usage: Rich graphics consume more memory
  • Mobile Constraints: Complex packaging process for iOS/Android
  • Documentation: Some documentation may be outdated

Key Links

Code Examples

Hello World Application

# hello_world.py
import kivy
kivy.require('2.1.0')

from kivy.app import App
from kivy.uix.label import Label


class MyApp(App):
    def build(self):
        return Label(text='Hello, Kivy!')


if __name__ == '__main__':
    MyApp().run()

Login Screen (Python + KV Language)

# login_app.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button


class LoginScreen(GridLayout):
    def __init__(self, **kwargs):
        super(LoginScreen, self).__init__(**kwargs)
        self.cols = 2
        
        # Username input
        self.add_widget(Label(text='Username'))
        self.username = TextInput(multiline=False)
        self.add_widget(self.username)
        
        # Password input
        self.add_widget(Label(text='Password'))
        self.password = TextInput(password=True, multiline=False)
        self.add_widget(self.password)
        
        # Login button
        self.add_widget(Label(text=''))  # Empty cell
        login_btn = Button(text='Login')
        login_btn.bind(on_press=self.on_login)
        self.add_widget(login_btn)
    
    def on_login(self, instance):
        print(f'Username: {self.username.text}')
        print(f'Password: {self.password.text}')


class LoginApp(App):
    def build(self):
        return LoginScreen()


if __name__ == '__main__':
    LoginApp().run()

Paint Application

# paint_app.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line
from random import random


class PaintWidget(Widget):
    def on_touch_down(self, touch):
        with self.canvas:
            # Generate consistent brightness colors using HSV mode
            color = (random(), 1., 1.)
            Color(*color, mode='hsv')
            
            # Draw circle at touch point
            d = 30.
            Ellipse(pos=(touch.x - d/2, touch.y - d/2), size=(d, d))
            
            # Start line drawing
            touch.ud['line'] = Line(points=(touch.x, touch.y))

    def on_touch_move(self, touch):
        # Extend line on touch move
        if 'line' in touch.ud:
            touch.ud['line'].points += [touch.x, touch.y]


class PaintApp(App):
    def build(self):
        parent = Widget()
        
        # Paint area
        self.painter = PaintWidget()
        parent.add_widget(self.painter)
        
        # Clear button
        clearbtn = Button(text='Clear', size_hint=(None, None), size=(100, 50))
        clearbtn.bind(on_release=self.clear_canvas)
        parent.add_widget(clearbtn)
        
        return parent

    def clear_canvas(self, obj):
        self.painter.canvas.clear()


if __name__ == '__main__':
    PaintApp().run()

Layout using KV Language

# kv_app.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder

# Define layout using KV language
Builder.load_string('''
<RootWidget>:
    orientation: 'vertical'
    padding: 20
    spacing: 10
    
    Label:
        text: 'Kivy Application'
        size_hint_y: None
        height: 50
        font_size: 24
    
    BoxLayout:
        orientation: 'horizontal'
        spacing: 10
        
        Button:
            text: 'Button 1'
            on_press: root.button_pressed(self.text)
        
        Button:
            text: 'Button 2'
            on_press: root.button_pressed(self.text)
        
        Button:
            text: 'Button 3'
            on_press: root.button_pressed(self.text)
    
    TextInput:
        id: text_input
        text: 'Enter text here'
        multiline: False
        size_hint_y: None
        height: 30
    
    Label:
        id: result_label
        text: 'Results will appear here'
        text_size: self.size
        halign: 'center'
        valign: 'middle'
''')


class RootWidget(BoxLayout):
    def button_pressed(self, button_text):
        # Access widgets using IDs
        self.ids.result_label.text = f'{button_text} was pressed'
        input_text = self.ids.text_input.text
        print(f'Button: {button_text}, Input: {input_text}')


class KVApp(App):
    def build(self):
        return RootWidget()


if __name__ == '__main__':
    KVApp().run()

Animation and Properties

# animation_app.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.graphics import Color, Rectangle


class AnimationWidget(Widget):
    def __init__(self, **kwargs):
        super(AnimationWidget, self).__init__(**kwargs)
        
        # Draw background
        with self.canvas:
            Color(0.2, 0.6, 0.8)  # Blue background
            self.rect = Rectangle(size=(100, 100), pos=(50, 50))
        
        # Animation button
        btn = Button(text='Start Animation', size_hint=(None, None), 
                    size=(200, 50), pos=(10, 10))
        btn.bind(on_press=self.start_animation)
        self.add_widget(btn)
    
    def start_animation(self, instance):
        # Rectangle animation
        anim = Animation(pos=(300, 200), size=(150, 150), duration=2)
        anim += Animation(pos=(50, 50), size=(100, 100), duration=2)
        anim.repeat = True
        anim.bind(on_progress=self.update_rect)
        anim.start(self)
    
    def update_rect(self, animation, widget, progress):
        # Update rectangle during animation
        self.rect.pos = (50 + progress * 250, 50 + progress * 150)
        self.rect.size = (100 + progress * 50, 100 + progress * 50)


class AnimationApp(App):
    def build(self):
        return AnimationWidget()


if __name__ == '__main__':
    AnimationApp().run()

Mobile Packaging (Buildozer Configuration)

# buildozer.spec
[app]
title = My Kivy App
package.name = mykivyapp
package.domain = org.example

source.dir = .
source.include_exts = py,png,jpg,kv,atlas

version = 0.1
requirements = python3,kivy

[buildozer]
log_level = 2

[app:android]
arch = arm64-v8a,armeabi-v7a

[app:android.gradle_dependencies]
implementation 'com.google.android.material:material:1.0.0'

[app:android.permissions]
android.permission.INTERNET
android.permission.WRITE_EXTERNAL_STORAGE

Project Setup and Execution

# Create virtual environment (recommended)
python -m venv kivy_env
source kivy_env/bin/activate  # Linux/macOS
# or
kivy_env\Scripts\activate  # Windows

# Install Kivy
python -m pip install "kivy[base]" kivy_examples

# Run application
python hello_world.py

# Build for Android (using Buildozer)
pip install buildozer
buildozer android debug

# Create desktop executable (using PyInstaller)
pip install pyinstaller
pyinstaller --onefile --windowed hello_world.py

# Run Kivy examples
python -m kivy.examples.demo.showcase

Example Project Structure

my_kivy_app/
├── main.py                 # Main application
├── screens/
│   ├── __init__.py
│   ├── home_screen.py      # Home screen
│   └── settings_screen.py  # Settings screen
├── widgets/
│   ├── __init__.py
│   └── custom_button.py    # Custom widgets
├── layouts/
│   ├── home.kv            # Home screen layout
│   └── settings.kv        # Settings screen layout
├── assets/
│   ├── images/
│   └── sounds/
├── buildozer.spec         # Android/iOS build config
├── requirements.txt
└── README.md