InputBorder is now a class hierarchy; loose border props removed
This guide is accurate as of Flet 1.0.0. Later releases might add new APIs or additional migration paths.
The breaking changes and deprecations index lists the guides created for each release.
Summary
Flet 1.0.0 replaced the InputBorder enum with a hierarchy of classes that
mirrors Flutter's
InputBorder:
OutlineInputBorder— a rounded rectangle around all sides (side,border_radius,gap_padding)UnderlineInputBorder— a line along the bottom edge (side,border_radius)ft.InputBorder.none()— draws nothing
At the same time, the loose border properties on TextField, Dropdown,
DropdownM2, and CupertinoTextField were removed: border_radius,
border_width, border_color, focused_border_width, and
focused_border_color. Everything they expressed (and more) now lives on the
border property, which accepts a single InputBorder or a dictionary mapping
ControlStates to InputBorders.
Background
The enum plus five loose properties could not represent Flutter's actual API:
gap_padding wasn't exposed, UnderlineInputBorder's corner radius was
ignored, and error/disabled borders couldn't be styled at all. Each new
Flutter border property would have required another top-level control property.
The class hierarchy scales with Flutter instead: new border types, such as the
ShapedInputBorder added in Flutter 3.44, become new classes rather than more
properties on every form field.
Migration guide
Border style
Code before migration:
ft.TextField(border=ft.InputBorder.OUTLINE)
ft.TextField(border=ft.InputBorder.UNDERLINE)
ft.TextField(border=ft.InputBorder.NONE)
Code after migration:
ft.TextField(border=ft.OutlineInputBorder()) # the default; can be omitted
ft.TextField(border=ft.UnderlineInputBorder())
ft.TextField(border=ft.InputBorder.none())
Corner radius, color, and width
Code before migration:
ft.TextField(
border_radius=30,
border_color=ft.Colors.GREEN_800,
border_width=2,
)
Code after migration:
ft.TextField(
border=ft.OutlineInputBorder(
border_radius=30,
side=ft.BorderSide(width=2, color=ft.Colors.GREEN_800),
),
)
To fully remove the border (previously border_width=0 or
border_color=ft.Colors.TRANSPARENT), use border=ft.InputBorder.none(), or
side=ft.BorderSide.none() to keep the outline's shape for the fill.
Unlike the old border_color, an explicit side on a single border applies
to the enabled state only — the focused border stays theme-colored
(primary, 2px). To tint the focused border too, as the old code did, use the
per-state form below.
Focused (and other per-state) borders
Code before migration:
ft.TextField(
border_radius=30,
border_color=ft.Colors.GREEN_800,
focused_border_color=ft.Colors.GREEN_ACCENT_400,
focused_border_width=5,
)
Code after migration:
ft.TextField(
border={
ft.ControlState.DEFAULT: ft.OutlineInputBorder(
border_radius=30,
side=ft.BorderSide(color=ft.Colors.GREEN_800),
),
ft.ControlState.FOCUSED: ft.OutlineInputBorder(
border_radius=30,
side=ft.BorderSide(width=5, color=ft.Colors.GREEN_ACCENT_400),
),
},
)
Supported state keys are DEFAULT, FOCUSED, ERROR, and DISABLED — the
error and disabled borders were not stylable before. A state entry without an
explicit side falls back to the DEFAULT entry's side, matching the old
focused_border_color or border_color color behavior.
The ERROR entry covers both error states: the field showing an error while
unfocused and while focused. When it carries no explicit side, the focused
variant is drawn at the thicker Material focus weight.
Note that an explicit side renders at exactly the width it specifies —
ft.BorderSide defaults to width 1. The old implicit focused width of 2
(applied when neither focused_border_width nor border_width was set) is
gone: pass side=ft.BorderSide(width=2, ...) in the FOCUSED entry to keep
the previous focus emphasis.
DropdownM2 menu corners
DropdownM2.border_radius used to round both the input field and the open
menu. Those are now separate: border shapes the field, and the new
menu_border_radius shapes the menu. Set both to keep the old look.
Code before migration:
ft.DropdownM2(border_radius=20)
Code after migration:
ft.DropdownM2(
border=ft.OutlineInputBorder(border_radius=20),
menu_border_radius=20,
)
Reading and comparing borders
ft.InputBorder is no longer an enum, so code that inspects a border rather
than setting one needs updating. There are no enum members to compare against
or iterate.
Code before migration:
if field.border == ft.InputBorder.NONE:
...
for style in ft.InputBorder:
...
Code after migration:
if field.border == ft.InputBorder.none():
...
for style in (
ft.OutlineInputBorder(),
ft.UnderlineInputBorder(),
ft.InputBorder.none(),
):
...
Borders are compared by value, so ft.OutlineInputBorder(border_radius=4)
equals ft.OutlineInputBorder() — 4 is the default. To test only the kind of
border, use isinstance(field.border, ft.OutlineInputBorder). Note that
field.border may also hold a ControlState dictionary rather than a single
border.
Behavior changes to be aware of
- Theme-driven border colors by default (Material controls:
TextField,Dropdown,DropdownM2). Previously the enabled border was always drawn black unlessborder_colorwas set — including in dark mode. Now a border without an explicitsidelets the Material theme resolve the color and width per state: focused uses the primary color, error the error color, and the enabled color comes from the theme's outline (or, forfilledfields, its active-indicator color). This is the Flutter default and works correctly with dark mode and custom themes. - An explicit
sideon a single border styles the enabled state only. Previouslyborder_coloralso tinted the focused border. Now the focused, error, and disabled states stay theme-resolved (focused: primary color, 2px) unless you use theControlStatedictionary form — see the per-state example above for restoring the old focused look. - Underline borders now honor a corner radius. The old
border_radiusprop was applied only to outlined borders and silently ignored whenborderwasInputBorder.UNDERLINE.UnderlineInputBorder.border_radiusis now passed through to Flutter, where it rounds the corners of the decoration's container — visible when the field isfilled, because the fill is clipped to that radius. The border itself is still drawn as a single line along the bottom edge. It defaults to4on the top corners. CupertinoTextFielddraws a box decoration rather than a Material input decoration, so it translates the border differently. Leavingborderunset keeps the native iOS hairline, exactly as before. Two explicit values change appearance:ft.OutlineInputBorder()without asidenow also keeps that native hairline, whereInputBorder.OUTLINEused to paint a solid black 1px box (pass asideto draw your own); andft.InputBorder.none()now actually removes the border, whereInputBorder.NONEwas silently ignored. An outline with an explicitsidedraws on all sides, and an underline draws the bottom side only, now honoring a non-defaultborder_radiusfor the fill. Aborder_radiusequal to the outline default (4) is indistinguishable from unset and keeps the native radius of5. In theControlStatedictionary form, theDEFAULT,FOCUSEDandDISABLEDentries apply;ERRORis ignored, as this control does not render an error state.
Timeline
- Changed in:
1.0.0
References
- API documentation:
InputBorder,OutlineInputBorder,UnderlineInputBorder,ControlState - Flutter
InputBorderAPI - Issues and PRs: #6773
- Release notes: Flet 1.0.0