Your First Kivy App
The first Kivy app establishes the skeleton every later example reuses: an App subclass whose build() returns the root widget, a KV file that describes the UI, and event methods that react to the user. The division of labor is the lesson - the KV file owns the layout and the widget tree, the Python owns the behavior, and the two are joined by the id property and the App's access to the loaded widgets. Build a counter: a label showing a number and a button that increments it, which is small enough to see every moving part.
The App Subclass and build()
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class CounterApp(App):
def build(self):
# build() must return the root widget of the whole app
root = BoxLayout(orientation="vertical")
return root
# event handlers are ordinary methods, wired up in the KV file
def on_increment(self):
self.root.label.text = str(int(self.root.label.text) + 1)
if __name__ == "__main__":
CounterApp().run()
App is the application object: it creates the window, starts the render and event loops, and calls your build() once to get the root widget. build() can return a widget constructed in Python (as here) or load a KV file (the next section) - both are valid, and a real app usually does the KV load. .run() starts the loops and blocks until the window closes. The handler on_increment is an ordinary method on the App; nothing about it is special until the KV file wires a button's on_press to it - the wiring is the next piece.
The KV File: Describing the UI
from kivy.app import App
from kivy.lang import Builder
KV = """
CounterScreen:
label: lbl
Button:
text: "Count"
size_hint_y: None
height: 60
on_press: root.on_increment()
<CounterScreen>:
BoxLayout:
orientation: "vertical"
Label:
id: lbl
text: "0"
font_size: 48
size_hint_y: 0.5
"""
from kivy.uix.boxlayout import BoxLayout
class CounterScreen(BoxLayout):
from kivy.properties import ObjectProperty
label = ObjectProperty(None)
def on_increment(self):
self.label.text = str(int(self.label.text) + 1)
class CounterApp(App):
def build(self):
Builder.load_string(KV)
return CounterScreen()
if __name__ == "__main__":
CounterApp().run()
The KV file has two parts. The first block names a class (CounterScreen) and gives its children - the Button, and the assignment label: lbl that binds the screen's label property to the child with id: lbl. The second block, <CounterScreen>:, is the class's rule - the widget tree the class itself contains (here a BoxLayout with a Label). The id property is the join between KV and Python: it names a widget inside the rule, and the label: lbl line copies that reference into an ObjectProperty so self.label in Python is the Label. The button's on_press: root.on_increment() calls a method on the screen - root in a KV rule is the widget the rule is defining. Run it: the window shows a big "0" and a button, and each press increments the label. Every part of the Kivy app is now visible: App, KV tree, ids, properties, and an event handler.
Auto-Loading the KV File
# main.py - no Builder call needed:
# Kivy auto-loads main.kv because the file is named after the module
from kivy.app import App
class CounterApp(App):
pass # build() can be empty if main.kv provides the root
# the first widget class in main.kv becomes the root automatically
if __name__ == "__main__":
CounterApp().run()
When the .kv file is named after the Python module (main.py -> main.kv), Kivy loads it automatically at startup, and the first class defined in it becomes the app's root widget - so build() can be left empty. This is the fastest path to a working app and the one to use until you need programmatic control (a root chosen at runtime, multiple KV files, widgets built in Python). When the file is not named after the module, or you load it from a string, the explicit Builder.load_string/Builder.load_file plus a build() that returns the root is the manual path shown above.
Where Each Piece Lives
| Concern | Lives in |
|---|---|
| Window, loops, app lifecycle | the App subclass |
| Widget tree, layout, static property values | the KV file |
| Names of widgets Python needs | id: in KV + ObjectProperty in the class |
| User reactions (press, text, touch) | on_* methods, wired in KV with on_press: etc. |
| Root selection | build(), or auto-load when the KV is named after the module |
This table is the map of a Kivy app, and the discipline is to keep each concern in its row: layout in the KV, behavior in the methods, the join through ids and properties. The counter is the minimal example where every row is exercised, and the recipe manager in the following chapters is the same skeleton with more widgets and a ScreenManager.
Summary
The skeleton: an App subclass whose build() returns the root widget, a KV file that declares the widget tree (a class block plus a <ClassName> rule), and on_* methods that react to events. The join is the id property - named in the KV, copied into an ObjectProperty on the class so Python can reach the widget - and the event wiring, on_press: root.on_method(). Name the KV file after the module and Kivy auto-loads it and makes the first class the root, which is the fast path; use the explicit Builder and build() when the root must be chosen in code. The counter app - a label and an incrementing button - is the reference shape; keep layout in the KV, behavior in the methods, and the rest of the tutorial is this skeleton scaled up.