skip to content

In Flutter, how would you build a coupon card with a gradient, rounded corners, an edge-to-edge image and a dashed border, and where do BoxDecoration's limits force other tools?

level: seniorimportance: should knowfreq 32%

answer

  1. gradient and borderRadius in BoxDecoration
  2. decorations never clip the child
  3. Card's clipBehavior defaults to Clip.none
  4. BorderStyle has only none and solid
  5. uniform border colors with borderRadius

basics

~20 s

BoxDecoration supplies the gradient, radius and shadow, but it never clips the child and cannot dash a border. Clip the image with ClipRRect or clipBehavior (Card defaults to Clip.none), and draw the dashed outline with a custom painter or a package.

solid answer

~40 s

I start with a `BoxDecoration`: `gradient: LinearGradient(...)`, `borderRadius: BorderRadius.circular(16)` and a soft `boxShadow`, painted by a `DecoratedBox` or `Container`. The image then bleeds over the rounded corners, because decorations paint behind the child and never clip it; I add `clipBehavior: Clip.antiAlias` on the `Container`, or a `ClipRRect` with the same radius. If I use a Material 3 `Card` instead, I must set `clipBehavior` there too, since it defaults to `Clip.none`. The dashed edge is where `BoxDecoration` stops: `BorderStyle` has only `none` and `solid`, so a dashed or perforated outline needs a custom painter or a package. Two assertions to know: a `borderRadius` with a `Border` whose sides have different colors, and `BoxShape.circle` combined with a `borderRadius`.

go deeper

for a junior

Know BoxDecoration's main fields, gradient, borderRadius, boxShadow and border, and that a decoration does not clip its child.

for a middle

Explain the clipping options, Card's Clip.none default, and the assertions for circle with radius and non-uniform border colors.

for a senior

Build the card so shadows survive clipping, keep clipping minimal for raster cost, and plan a painter or ShapeBorder for the dashed edge.

for a principal

Decide when a shared coupon component justifies a custom ShapeBorder versus a package, weighing maintenance against visual fidelity.

## What BoxDecoration can paint `BoxDecoration` is the everyday way to style a rectangle in Flutter. Its constructor takes `color`, `image`, `border`, `borderRadius`, `boxShadow`, `gradient`, `backgroundBlendMode` and `shape` (default `BoxShape.rectangle`). Paint order, bottom to top: 1. `boxShadow` outside the box. 2. `color`, then `gradient` on top of it. 3. `image` (a `DecorationImage`). 4. `border`. For the coupon's surface this is enough: ```dart import 'package:flutter/material.dart'; class CouponCard extends StatelessWidget { const CouponCard({super.key, required this.image, required this.body}); final ImageProvider image; final Widget body; @override Widget build(BuildContext context) { return Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: <Color>[Color(0xFFFFF3E0), Color(0xFFFFCC80)], ), borderRadius: BorderRadius.circular(16), boxShadow: const <BoxShadow>[ BoxShadow(color: Color(0x33000000), blurRadius: 12, offset: Offset(0, 4)), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ Image(image: image, height: 120, fit: BoxFit.cover), Padding(padding: const EdgeInsets.all(16), child: body), ], ), ); } } ``` ## Limit 1: decorations do not clip A decoration paints **behind** the child (or in front, with `DecorationPosition.foreground`), but the child is laid out and painted in the full rectangle. Without clipping, the image's square corners poke out of the rounded card. Options: - **`Container.clipBehavior`** (it requires a `decoration`): clips the child to the decoration's shape, as above. - **`ClipRRect`** with the same `borderRadius`, whose `clipBehavior` defaults to `Clip.antiAlias`. - **`Card(clipBehavior: Clip.antiAlias)`** when using Material's card. In the example, the shadow still shows because `Container` puts the clip inside the decoration, so only the child is clipped. Clipping has a raster cost, so clip only where content actually reaches the corners. ## Limit 2: Card defaults Material 3's `Card` (and `Card.filled`, `Card.outlined`) resolves `clipBehavior` from the widget, then `CardThemeData`, then the default **`Clip.none`**. Its Material 3 defaults are an elevation of `1.0`, a margin of `4.0` on every side and a 12-pixel corner radius. Developers who switch from a `Container` to a `Card` for its elevation often rediscover the bleeding corners for exactly this reason. ## Limit 3: no dashed borders `BorderSide.style` is a `BorderStyle`, and `BorderStyle` has two values: **`none` and `solid`**. There is no dashed or dotted style in `BoxDecoration`, `Border` or `ShapeDecoration`. A coupon's dashed or perforated edge therefore needs one of: - A custom painter that walks the rounded rectangle's path and draws dash segments. - A custom `ShapeBorder` subclass that paints dashes, usable in `ShapeDecoration` or a `Card`'s `shape`. - A pub package that implements one of the above. How to write that painter is a painting topic of its own; the interview point is knowing that the built-in decoration cannot do it, rather than hunting for a `dashed` flag. ## Assertions that catch mixed settings | Combination | Result in debug mode | |---|---| | `Container(color: ..., decoration: ...)` | Assertion: provide one or the other | | `BoxDecoration(shape: BoxShape.circle, borderRadius: ...)` | Assertion: a circle cannot have a border radius | | `borderRadius` with a `Border` whose sides differ in color | Error: a borderRadius can only be given on borders with uniform colors | | `backgroundBlendMode` without `color` or `gradient` | Assertion | | `Container(clipBehavior: ...)` without `decoration` | Assertion | ## Gradients and shadows in detail `LinearGradient`, `RadialGradient` and `SweepGradient` all work in `BoxDecoration.gradient`, and each takes optional `stops` to control where colors change. A gradient paints over `color`, so setting both is allowed but only the gradient shows where it is opaque. `BoxShadow` takes `color`, `offset`, `blurRadius` and `spreadRadius`; several shadows can be layered to approximate a designer's elevation. Colors in a shared coupon component should come from the theme or a `ThemeExtension` rather than hex literals, so brands and dark mode apply. ## Choosing the container widget - **`DecoratedBox`**: just the paint, `const`-constructible. - **`Container`**: paint plus padding, size, margin and clipping in one place, as in the example. - **`Card`**: Material elevation, surface tint and theme integration via `CardThemeData`; remember its clip default. - **`ClipRRect`**: clipping only, around whatever you already have. ## Diagnosing a card that "looks wrong" 1. Corners of an image show: add clipping. 2. Shadow missing: check whether a parent clips the whole card, or whether `Card` elevation was set to 0. 3. Border radius ignored on one side: check for a non-uniform `Border` or a `BorderRadius.only` with missing corners. 4. Dashed edge requested: plan a painter or a `ShapeBorder` rather than adjusting `BorderSide`.

  • In Flutter, why does clipping the coupon with an outer ClipRRect hide its BoxShadow while Container.clipBehavior does not?
    A `ClipRRect` around the decorated box clips everything it paints, including the shadow drawn outside the rounded rectangle. `Container.clipBehavior` inserts the clip inside the `DecoratedBox`, so only the child is clipped and the shadow stays visible. Put an outer clip only around content that has no shadow.
  • In Flutter, can a Card itself have a dashed outline?
    Not with a built-in option: `Card.shape` takes a `ShapeBorder`, and the stock borders use `BorderSide`, whose `BorderStyle` is only `none` or `solid`. You would pass a custom `ShapeBorder` subclass that paints dashes, or draw the dashes with a painter layered over the card.

saying these in an interview costs you the question

  • Believing BoxDecoration's borderRadius clips the child image
  • Looking for a dashed value in BorderStyle
  • Assuming Material 3 Card clips its content by default
  • Wrapping a shadowed card in ClipRRect and wondering where the shadow went
  • Combining BoxShape.circle with a borderRadius