Organizations today strive to deliver presentable UIs in their mobile apps for end users — a great visual experience brings users back.
When it comes to creating a beautiful UI with ease, Flutter has gained popularity since it’s fundamentally a UI Toolkit that can build beautiful, natively compiled, multi-platform applications from a single codebase.
Visual experiences can be enhanced in many ways, including subtle highlights such as colors, highlights around elements, and border decorations. Borders — in particular around the UI components — can provide intuitiveness as well as hierarchy and structure to the end user.
Borders vary in all shapes and sizes, from normal borders with a single color to ones with a gradient, which require different approaches in Flutter.
In this article, you will learn how to create borders in Flutter. Not just a typical default border, but more interesting ones that make widgets and containers stand out. Aside from the basics, you will also learn how to draw borders with gradients.
Let’s get started.
BoxDecorationThe Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
BoxDecorationBoxDecoration is an immutable class which allows you to paint a box by specifying various properties. When building elements such as a Container, it may be supplied as a decorative property. It is used to give decoration to the Widget where it is utilized, as the name indicates.
Supported decorations include color, gradient, background image, border, and shadow.
You can add borders by passing border properties to the BoxDecoration class. There are multiple ways to create an instance of this, such as:
Border() constructorBorder.all factory patternBorder.merge static methodA specified border on the Container is drawn on top of everything, including; color, gradient, and image.
Let’s begin with drawing a single-colored border around a widget, as demonstrated here:
// dart
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
border: Border.all(color: Colors.green[400], width: 3)
),
child: Text("I am inside a border.", style: TextStyle(fontSize: 24),),
);
In this example, the Text widget is wrapped by a container and holds properties such as border width and color using BoxDecoration. Alternatively, it is also possible to provide separate border properties to each side using the Border constructor instead of Border.all(...).
You can change the border radius using the borderRadius property inside BoxDecoration.
// dart
Container(
width: 100,
height:100,
decoration:BoxDecoration(
color: Colors.blue, // Color of the container
borderRadius: BorderRadius.circular(20.0), // Radius of the border
border: Border.all(
width: 8.0,
color: Colors.black, // Color of the border
)
)
)
TextFieldAn instance of the UnderlineInputBorder class with specific border properties such as color and width is supplied to the InputDecoration class as a parameter, in order to draw the border around TextField.
When the TextFieldis in focus —such as when the user attempts to type text into the field, the border becomes active. The border disappears as soon as the TextFieldloses focus.
// dart
Container(
child: TextField(
decoration: InputDecoration(
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
width: 2.0
),
),
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
hintText: "Enter your name"
),
)
)
Consider a border wrapped around text — it is actually a stroke that encloses every letter itself instead of the outer Container.
// dart
Stack(
children: [
Text(
'I am inside a border.',
style: TextStyle(
fontSize: 24,
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = Colors.green[400],
),
),
Text(
'I am inside a border.',
style: TextStyle(
fontSize: 24,
color: Colors.blue,
),
),
]
);
If you only use the first Text widget in the stack, it will only add a stroke; you will need to choose a Stack widget that shows the border and fills both of the widgets. Stacking two Text widgets on top of each other that have different font sizes will not work because the text is scaled from the center.
You’ve learned how to implement borders that are relatively basic. Now, let’s implement a gradient border around a Widget, rather than simply a single-toned border.
When decorating a border with a gradient, two properties are required:
Gradient, which contains the necessary information such as colors, etc.width of the border strokeFor this, you need to implement an instance of the CustomPainter class as shown in the below code:
// dart
class CustomGradient extends CustomPainter {
CustomGradient({this.gradient, this.sWidth});
final Gradient gradient;
final double sWidth;
final Paint p = Paint();
@override
void paint(Canvas canvas, Size size) {
Rect innerRect = Rect.fromLTRB(sWidth, sWidth, size.width - sWidth, size.height - sWidth);
Rect outerRect = Offset.zero & size;
p.shader = gradient.createShader(outerRect);
Path borderPath = _calculateBorderPath(outerRect, innerRect);
canvas.drawPath(borderPath, p);
}
Path _calculateBorderPath(Rect outerRect, Rect innerRect) {
Path outerRectPath = Path()..addRect(outerRect);
Path innerRectPath = Path()..addRect(innerRect);
return Path.combine(PathOperation.difference, outerRectPath, innerRectPath);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
Here, instead of drawing the four separate sides of the border, we have added two rectangles as shown in the example, one of which has the same size as the Widget — whereas the other rectangle has the stroke width subtracted from its original size. PathOperation.difference calculates the difference between the two rectangles, and the resulting difference acts as a stroke around the smaller rectangle.
To make the gradient work, we will need to add a shader to the paint object p. In the example above, createShader is the method which accepts the object of outerRect as an argument to make the gradient reach the edges.
Now, to make use of our custom class CustomGradient, we need an enclosing Widget that contains a child widget and draws our gradient around it. Have a look at this example:
class CustomGradientContainer extends StatelessWidget {
CustomGradientContainer({
@required gradient,
@required this.child,
this.strokeWidth = 3,
}) : this.painter = CustomGradient(
gradient: gradient, sWidth: strokeWidth
);
final CustomGradient painter;
final Widget child;
final VoidCallback onPressed;
final double strokeWidth;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: painter,
child: child
);
}
}
This will draw a gradient around the text. However, this border will be tightly bound around the text itself, which is not conducive to a presentable UI. To improve our UI, we need to insert some further customizations, such as rounded corners and padding.
Take a look at the example below to see how to provide a rounded border to the CustomGradient class:
// dart
class CustomGradient extends CustomPainter {
...
final Gradient gradient;
final double sWidth;
final Paint p = Paint(); // Existing declarations
final double bRadius; // Declare border radius
...
@override
void paint(Canvas canvas, Size size) {
... // Existing rectangle declarations
RRect innerRoundedRect = RRect.fromRectAndRadius(innerRect, Radius.circular(bRadius));
RRect outerRoundedRect = RRect.fromRectAndRadius(outerRect, Radius.circular(bRadius));
... // Existing PaintObject declarations
Path borderPath = _calculateBorderPath(outerRoundedRect, innerRoundedRect);
}
Path _calculateBorderPath(RRect outerRect, RRect innerRect) { // Update parameters
Path outerRectPath = Path()..addRRect(outerRect);
Path innerRectPath = Path()..addRRect(innerRect);
return Path.combine(PathOperation.difference, outerRectPath, innerRectPath);
}
...
}
Once the CustomGradient class is updated to specify the border radius property, you can modify CustomGradientContainer to draw the border radius as below:
// dart
class CustomGradientContainer extends StatelessWidget {
CustomGradientContainer({
@required gradient,
@required this.child,
@required this.onPressed,
this.strokeWidth = 3,
this.bRadius = 36, // Add border radius declaration
this.padding = 12 // Add padding declaration
}) : this.painter = GradientPainter(
gradient: gradient, strokeWidth: sWidth, borderRadius: bRadius // Add border radius declaration
);
final GradientPainter painter;
final Widget child;
final VoidCallback onPressed;
final double sWidth;
final double bRadius; // Add border radius
final double padding; // Add padding
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: painter,
child: Container( // Add container declaration
padding: EdgeInsets.all(padding), // Add padding declaration
child: child
)
);
}
}
Here’s what we have done: the constructor of the GradientBorderContainer widget has been modified to declare border the radius and padding. We have then declared the value of the padding to wrap a Container around the child Widget with the respective padding.
Using a CustomPainter class to create a border around a Widget provides a lot more flexibility for decorations in Flutter.
In addition to the basics, we also looked at how to create a changing gradient border around a Widget. You may change the gradient, padding, stroke width, and the width of the outline enclosing the border.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.

John Reilly discusses how software development has been changed by the innovations of AI: both the positives and the negatives.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
One Reply to "How to create simple and gradient borders in Flutter"
It would be really useful to see an image of what the result looks like before you go in to the code. At present there isn’t an image at all. It’s unlikely many people will try the code if they don’t know what the result will be.