Table Of Contents
Migrating from Kivy 2.x.x to Kivy 3.x.x¶
Introduction¶
Kivy 3.x.x introduces several changes and improvements compared to Kivy 2.x.x. This guide will help you migrate your existing Kivy 2.x.x codebase to Kivy 3.x.x.
Renamed modules and environment variables¶
Migration from kivy.core.audio to kivy.core.audio_output
In Kivy 3.x.x, the kivy.core.audio module has been renamed as kivy.core.audio_output.
Import Statement Changes
To migrate your code, you need to update the import statements in your codebase. For example, if you have the following import statement in your code:
from kivy.core.audio import SoundLoader
You need to update it to:
from kivy.core.audio_output import SoundLoader
Environment Variable Changes
The environment variable has also been renamed from KIVY_AUDIO to KIVY_AUDIO_OUTPUT.
If you were using the KIVY_AUDIO environment variable to specify audio provider preferences, you need to update it to KIVY_AUDIO_OUTPUT. For example:
in Python before importing Kivy:
import os
# Kivy 2.x.x
os.environ['KIVY_AUDIO'] = 'sdl3,gstplayer'
import kivy
# Kivy 3.x.x
os.environ['KIVY_AUDIO_OUTPUT'] = 'sdl3,gstplayer'
import kivy
Removals¶
Removal of `.play` property from `kivy.uix.video.Video` and `kivy.uix.videoplayer.VideoPlayer`
In Kivy 3.x.x, the .play property has been removed from the kivy.uix.video.Video and kivy.uix.videoplayer.VideoPlayer classes.
To migrate your code, you need to update the references to the .play property in your codebase. For example, if you have the following code in your Kivy 2.x.x codebase:
video = Video(source='video.mp4')
# Play the video
video.play = True
# Stop the video
video.play = False
You need to update it to:
video = Video(source='video.mp4')
# Play the video
video.state = 'play'
# Stop the video
video.state = 'stop'
# Pause the video
video.state = 'pause'
Removal of `padding_x` and `padding_y` Properties from `kivy.uix.textinput.TextInput`
In Kivy 3.x.x the padding_x and padding_y properties have been removed from the kivy.uix.textinput.TextInput class. Instead, padding is now managed through the unified padding property.
To update your code, replace instances of padding_x and padding_y with the padding property.
The padding property accepts a list of values, allowing for more flexible padding configurations:
[horizontal, vertical] — e.g., [10, 10]
[padding_left, padding_top, padding_right, padding_bottom] — e.g., [10, 5, 10, 5]
For more details on how to use the padding property, please refer to the related documentation.
Removal of `file_encodings` Property from `kivy.uix.filechooser.FileChooserController`
In Kivy 3.x.x, the file_encodings property has been removed from the kivy.uix.filechooser.FileChooserController class.
The file_encodings property was deprecated and it was kept for backward compatibility, however it was just ignored and not used internally.
To migrate your code, you just need to remove any references to the file_encodings property in your codebase.
Removal of deprecated `on_dropfile` Window event name
In Kivy 3.x.x, the previously deprecated on_dropfile event name has been removed. Use on_drop_file instead.
The event was renamed in Kivy 2.1.0, so any remaining compatibility code that still binds to on_dropfile now needs to be updated.
# Kivy 2.x.x (legacy/deprecated name)
from kivy.core.window import Window
def handle_drop(window, filename):
print(filename)
Window.bind(on_dropfile=handle_drop)
# Kivy 3.x.x
from kivy.core.window import Window
def handle_drop(window, filename, x, y, *args):
print(filename, x, y)
Window.bind(on_drop_file=handle_drop)
If you dispatch or override the event directly, also rename any on_dropfile method implementations to on_drop_file.
Removal of the Kv-lang Templates feature
In Kivy 3.x.x, the deprecated Kivy language Templates feature (introduced in 1.0.5, deprecated in 1.7.0) has been removed. The following are gone:
The
[Name@Base]:Kv-lang template syntax (any kv file that contains a[...]:selector will now raise aParserExceptionat load time).Builder.template(name, **ctx)and theBuilder.templatesdict.Factory.is_template()and theis_template=keyword argument ofFactory.register().
Migrate to dynamic classes (<Name@Base>:). Dynamic classes have largely
superseded templates since Kivy 1.7.0 and support normal Kivy properties,
binding and inheritance.
Migrating a template in `.kv`
# Kivy 2.x.x
[IconItem@BoxLayout]:
Image:
source: ctx.image
Label:
text: ctx.title
# Kivy 3.x.x
<IconItem@BoxLayout>:
image: ''
title: ''
Image:
source: root.image
Label:
text: root.title
Note the two changes: [...]: becomes <...>:, and ctx.foo references
become root.foo references against properties declared on the rule itself.
Migrating `Builder.template(…)` instantiation
# Kivy 2.x.x
from kivy.lang import Builder
icon = Builder.template('IconItem', title='Hello', image='myimage.png')
# Kivy 3.x.x
from kivy.factory import Factory
icon = Factory.IconItem()
icon.title = 'Hello'
icon.image = 'myimage.png'
Because dynamic-class properties are added to the widget by the rule (rather
than declared on the class), they are not yet present when __init__
processes its kwargs - so pass values via setattr (or property assignment)
after construction rather than as constructor kwargs. If you need
constructor-kwarg support, define the widget as a regular Python class with
explicit Property declarations instead.
AccordionItem: `title_template` and `title_args` replaced by `title_class`
The AccordionItem widget previously used the Kv-lang
templates feature to render its title bar via the title_template (string)
and title_args (dict) properties. Both properties have been removed.
The replacement is title_class. It accepts
either a class object or a Factory-resolvable string. The class is instantiated
with two keyword arguments: title and item.
To customise the appearance of the title widget, subclass
AccordionItemTitle (or write any widget that
accepts title and item kwargs) and pass it via title_class.
See also
title_class documentation.
Clock¶
Improved @triggered Decorator Behavior, Instance Isolation and Debouncing
In Kivy 3.x.x, the triggered() decorator has been significantly
improved. Previously, when used as a method decorator, it shared a single trigger
and state across all instances of a class. This meant that calling the method on
one instance would throttle calls on all other instances, and arguments from
different instances could overwrite each other.
Behavior Changes
Improved Instance Isolation: Each instance now has its own isolated trigger and argument storage. Calling a triggered method on
widget_ano longer affectswidget_b.Improved Lazy Initialization: Triggers are now created only when the decorated function is first called, improving initialization performance.
New is_triggered Property: A new
is_triggeredproperty was added to the decorated function/method, allowing you to check if a call is currently pending.New debounce Parameter: A new
debounce=False(default) parameter was added.Throttling (default): Subsequent calls while a trigger is active update the arguments but do not reset the timer. The function fires once after the initial timeout.
Debouncing (
debounce=True): Subsequent calls cancel any pending execution and reschedule it. The function only fires after the caller stops calling it for the duration of thetimeout.
Migration Impact
This is primarily a bug fix and a set of new features. It should not
require code changes for most applications. However, if your codebase
intentionally relied on the legacy shared throttling behavior across different
instances, you can restore this behavior by using the ``@classmethod``
decorator above @triggered.
This ensures the trigger is bound to the class object rather than individual instances, restoring the shared behavior in an idiomatic way.
class MyWidget(Widget):
# Default in 3.x.x: Isolated per instance
@triggered(0.1)
def sync_ui(self, *args):
pass
# Shared behavior (same as legacy 2.x.x): Shared by all instances
@classmethod
@triggered(0.1)
def sync_shared_data(cls, *args):
pass
# Optional: Debouncing (0.1s from the LAST call)
@triggered(0.1, debounce=True)
def search_input(self, text):
pass
AliasProperty¶
Fixed Dispatch Inconsistencies for the Initial Value and None Getter Results
In Kivy 3.x.x, two related bugs in AliasProperty dispatch
have been fixed (see #6901).
Behavior Changes
Initial value is now always dispatched. Previously, whether the very first value of an
AliasPropertywas dispatched (e.g. when one of itsbind-ed dependencies changed before the alias property had ever been read) depended on unrelated implementation details, such as whether the alias property had already been read once, or whether anon_<name>handler existed on the class (which forced an early read during__init__). Now, the first time an alias property’s value is established, it is always dispatched exactly once.Fixed missed dispatches when the getter returns
None. Previously, the alias property’s internally tracked value was only kept up to date whencache=True. This meant that withcache=False, real changes could be silently missed (or, if the getter’s result happened to equal a leftover default, dropped) whenever the getter legitimately returnedNone. The internal tracking is now always kept in sync, regardless ofcache, so changes are correctly detected and dispatched even when the getter returnsNone.
from kivy.event import EventDispatcher
from kivy.properties import NumericProperty, AliasProperty
class Rect(EventDispatcher):
width = NumericProperty(0)
height = NumericProperty(0)
def _get_aspect_ratio(self):
return (self.width / self.height) if self.height else None
aspect_ratio = AliasProperty(
_get_aspect_ratio, None, bind=('width', 'height'), cache=True)
def on_aspect_ratio(self, instance, value):
print(f'aspect_ratio: {value}')
# Kivy 2.x.x
r = Rect(width=100)
r.height = 1 # prints "aspect_ratio: 100.0"
r.height = 0 # missing dispatch - `on_aspect_ratio` is NOT called
# Kivy 3.x.x
r = Rect(width=100)
r.height = 1 # prints "aspect_ratio: 100.0"
r.height = 0 # prints "aspect_ratio: None"
Migration Impact
This is primarily a bug fix. It should not require code changes for most applications, and in most cases will surface events (dispatches) that your application was previously and incorrectly not receiving.
The one behavior to be aware of: code that binds to an AliasProperty
(directly, via bind()/fbind(), or implicitly via an on_<name>
handler) and relies on its dependencies changing during __init__ may now
observe an extra initial dispatch call that previously did not fire in some
constructions. If your callback assumed it would only ever be called for
genuine value transitions, guard against redundant no-op work as needed,
for example by comparing against the previously known value inside the
callback itself.
Mouse Input Provider¶
Multiple Mouse Buttons Can Now Be Held Down Simultaneously
In Kivy 3.x.x, the mouse input provider
(MouseMotionEventProvider) has been
fixed to correctly track multiple mouse buttons that are held down at the
same time when multitouch simulation is disabled (disable_multitouch)
(see #3597).
Behavior Changes
Multiple simultaneous button-drags are now tracked independently. Previously, the provider only tracked a single “current drag” at a time. If a second mouse button was pressed while a first button was still held down, the second button’s press was silently ignored (no
on_touch_downwas dispatched for it) until the first button was released. Now, each button gets its own independent touch whendisable_multitouchis set, so pressing a second (or third) button while another is held correctly dispatches its ownon_touch_down/on_touch_move/on_touch_upsequence.This change only affects behavior when
disable_multitouchis active. When multitouch simulation is enabled (the default), only one simulated touch (the red dot) is tracked at a time, as before.
from kivy.app import App
from kivy.uix.widget import Widget
class MainWindow(Widget):
def on_touch_down(self, touch):
print(f'{touch.button} is being pressed')
class TestingApp(App):
def build(self):
return MainWindow()
if __name__ == '__main__':
TestingApp().run()
# config: [input] mouse = mouse,disable_multitouch
# Kivy 2.x.x
# press and hold left -> "left is being pressed"
# press right while left is still held -> (nothing printed)
# Kivy 3.x.x
# press and hold left -> "left is being pressed"
# press right while left is still held -> "right is being pressed"
Migration Impact
This is primarily a bug fix. It should not require code changes for
most applications, and mainly benefits applications using
disable_multitouch that need to respond to more than one mouse button
at a time (e.g. left + right held together).
SVG¶
Removal of the experimental kivy.graphics.svg module
In Kivy 3.x.x, the experimental Svg canvas instruction in
kivy.graphics.svg has been removed, along with its example scripts under
examples/svg/ (benchmark.py, main.py, main-smaa.py) and its
Factory registration. The module had been marked experimental since its
introduction and is superseded by the new SVG support added in Kivy 3.x.x.
Replacement: SvgWidget / AsyncSvgWidget
For most use cases, drop in SvgWidget (local sources)
or AsyncSvgWidget (network sources):
# Kivy 2.x.x
from kivy.graphics.svg import Svg
with widget.canvas:
Svg('image.svg')
# Kivy 3.x.x
from kivy.uix.svg import SvgWidget
widget.add_widget(SvgWidget(source='image.svg'))
In KV language:
# Kivy 3.x.x
SvgWidget:
source: 'image.svg'
Replacement: kivy.core.svg image provider
For loading SVGs through the standard image pipeline (for example as a
texture for Image), the new kivy.core.svg
provider is selected automatically when an .svg source is loaded; no
explicit import or Factory registration is required.
Factory registration
The Svg Factory entry that pointed at kivy.graphics.svg has been
removed. SvgWidget and AsyncSvgWidget are registered in the Factory
under their own names and can be used directly from KV.
Application Storage Directories¶
Linux user_data_dir Path Change (XDG Compliance Fix)
In Kivy 3.x.x, the App.user_data_dir path on Linux has been corrected to follow
the XDG Base Directory specification. Previously, it incorrectly used XDG_CONFIG_HOME
(for configuration files); it now correctly uses XDG_DATA_HOME (for application data).
Path Changes on Linux:
Property |
Kivy 2.x.x |
Kivy 3.x.x |
|---|---|---|
|
|
|
Impact:
If your Linux application uses App.user_data_dir to store user data, the data
will now be stored in a different location after upgrading to Kivy 3.x.x. This is
the correct XDG-compliant location, but existing apps may need to migrate their data.
Migration Options:
Manual Migration (Recommended for production apps)
Move existing data from the old location to the new location during app startup:
Note: Windows, macOS, iOS, and Android paths are unchanged.
New App.user_cache_dir Property
Kivy 3.x.x introduces a new App.user_cache_dir property for temporary/cache data
that the system may delete at any time.
This is not a breaking change - it’s a new optional property. Existing apps continue to work unchanged.
Platform Paths:
Windows:
%APPDATA%\<app_name>\CachemacOS:
~/Library/Caches/<app_name>Linux:
~/.cache/<app_name>(respects$XDG_CACHE_HOME)Android:
Context.getCacheDir()iOS:
~/Library/Caches/<app_name>
New KIVY_DESKTOP_PATH_ID Environment Variable
Kivy 3.x.x introduces KIVY_DESKTOP_PATH_ID to set user-friendly application
directory names on desktop platforms.
This is not a breaking change - it’s opt-in. Existing apps continue to work unchanged unless you explicitly set this environment variable.
Key Feature:
Setting KIVY_DESKTOP_PATH_ID creates an application-specific location for
the .kivy directory containing the config and log files. Without
KIVY_DESKTOP_PATH_ID, the config and logs are placed in a single global
.kivy directory (~/.kivy).
This means multiple Kivy applications can now have their own isolated configuration and log directories, preventing conflicts between different applications.
When Set:
The variable provides a human-readable application title for directories, making it easier for end users to identify your app’s directories when browsing their filesystem.
Example:
import os
os.environ['KIVY_DESKTOP_PATH_ID'] = 'My Photo Editor'
from kivy.app import App
# On Windows, creates: %APPDATA%\My_Photo_Editor\.kivy
# Instead of: %APPDATA%\photoeditor\.kivy
Priority:
KIVY_DESKTOP_PATH_ID takes highest priority and affects:
KIVY_HOMEdirectory (overridesKIVY_HOMEenv var and venv detection)App.user_data_dirdirectoryApp.user_cache_dirdirectory
Platform Behavior:
Desktop platforms (Windows, macOS, Linux): Uses normalized path_id for directory names
Mobile platforms (iOS, Android): Ignored - uses
App.nameas before
Desktop Path Examples with KIVY_DESKTOP_PATH_ID=’My Photo Editor’:
Directory |
Path |
|---|---|
KIVY_HOME |
|
user_data_dir |
|
user_cache_dir |
|
Warning:
If you set KIVY_DESKTOP_PATH_ID in an existing app, your data will move to a new
location. You may need to migrate existing data (see Linux migration example above).
For complete documentation, see Controlling the environment and examples/desktop_path_id/.
```