ashlarui

Badge

A small label: a status, a count, a tag.

Badge, as it looks in an app Badge, as it looks in an app, dark
import 'package:ashlar_icons/ashlar_icons.dart';
import 'package:flutter/widgets.dart';

import 'ui/badge.dart';

class BadgeDemo extends StatelessWidget {
  const BadgeDemo({super.key});

  @override
  Widget build(BuildContext context) => const Wrap(
    spacing: 8,
    runSpacing: 8,
    alignment: WrapAlignment.center,
    children: [
      Badge(child: Text('New')),
      Badge(variant: BadgeVariant.secondary, child: Text('Draft')),
      Badge(variant: BadgeVariant.destructive, child: Text('Overdue')),
      Badge(
        variant: BadgeVariant.outline,
        leading: AshlarIcon(AshlarIconsRoundedStroke.check),
        child: Text('Paid'),
      ),
    ],
  );
}

Try its options

Each option its constructor takes, to change and see; the code below it is what you chose, to copy.

Badge with controls, as it looks in an app Badge with controls, as it looks in an app, dark

Add it

In your Flutter project:

$ ashlar add badge

It writes theme.dart, palette.dart and badge.dart into lib/ui, keeping any you already have.

Examples

Other ways to use it, each running live, with its code.

Variants

Every fill, with and without an icon.

Variants, as it looks in an app Variants, as it looks in an app, dark
// Variants: Every fill, with and without an icon.
import 'package:ashlar_icons/ashlar_icons.dart';
import 'package:flutter/widgets.dart';

import 'ui/badge.dart';

class BadgeVariantsExample extends StatelessWidget {
  const BadgeVariantsExample({super.key});

  @override
  Widget build(BuildContext context) => const Wrap(
    spacing: 8,
    runSpacing: 8,
    children: [
      Badge(child: Text('Primary')),
      Badge(variant: BadgeVariant.secondary, child: Text('Secondary')),
      Badge(variant: BadgeVariant.destructive, child: Text('Overdue')),
      Badge(
        variant: BadgeVariant.outline,
        leading: AshlarIcon(AshlarIconsRoundedStroke.check),
        child: Text('Verified'),
      ),
      Badge(variant: BadgeVariant.ghost, child: Text('Ghost')),
      Badge(variant: BadgeVariant.link, child: Text('Link')),
    ],
  );
}

API

Read from the source above: each parameter with its type, its default, and what it is for.

BadgeVariantenum

ValueWhat it is for
primary
secondary
destructive
outline
ghostNo fill or border: a quiet label beside other text.
link Text in the primary colour, underlined: a label that leads somewhere.

Badgeclass

A small label: a status, a count, a tag. leading is usually a 12px icon.

Parameter Type Default What it is for
childrequired Widget Its text.
variant BadgeVariant BadgeVariant.primary Its fill: primary, secondary, destructive, outline, ghost or link.
leading Widget? Before its text: usually an icon.

Source

What lands in your app. It is yours from then on: change it as you would your own code, and ashlar add will not write over it.

lib/ui/badge.dart 89 lines
// Ashlar UI: Badge. Copied into your app by `ashlar add badge`; it is yours to change.

import 'package:flutter/widgets.dart';

import 'theme.dart';

enum BadgeVariant {
  primary,
  secondary,
  destructive,
  outline,

  /// No fill or border: a quiet label beside other text.
  ghost,

  /// Text in the primary colour, underlined: a label that leads somewhere.
  link,
}

/// A small label: a status, a count, a tag. [leading] is usually a 12px icon.
///
/// ```dart
/// Badge(child: Text('New'))
/// Badge(variant: BadgeVariant.outline, leading: AshlarIcon(AshlarIconsRoundedStroke.check, size: 12), child: Text('Paid'))
/// ```
class Badge extends StatelessWidget {
  const Badge({
    super.key,
    required this.child,
    this.variant = BadgeVariant.primary,
    this.leading,
  });

  /// Its text.
  final Widget child;

  /// Its fill: primary, secondary, destructive, outline, ghost or link.
  final BadgeVariant variant;

  /// Before its text: usually an icon.
  final Widget? leading;

  @override
  Widget build(BuildContext context) {
    final t = AshlarTheme.of(context);
    final c = t.colors;
    final (Color bg, Color fg, Color? border) = switch (variant) {
      BadgeVariant.primary => (c.primary, c.primaryForeground, null),
      BadgeVariant.secondary => (c.secondary, c.secondaryForeground, null),
      BadgeVariant.destructive => (
        c.destructive,
        c.destructiveForeground,
        null,
      ),
      BadgeVariant.outline => (const Color(0x00000000), c.foreground, c.border),
      BadgeVariant.ghost => (const Color(0x00000000), c.foreground, null),
      BadgeVariant.link => (const Color(0x00000000), c.primary, null),
    };
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
      decoration: BoxDecoration(
        color: bg,
        borderRadius: BorderRadius.circular(t.radiusMd),
        border: Border.all(color: border ?? const Color(0x00000000)),
      ),
      child: DefaultTextStyle.merge(
        style: t.text.xs.copyWith(
          color: fg,
          fontWeight: FontWeight.w500,
          decoration: variant == BadgeVariant.link
              ? TextDecoration.underline
              : null,
          decorationColor: fg,
        ),
        maxLines: 1,
        child: IconTheme.merge(
          data: IconThemeData(color: fg, size: 12),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (leading != null) ...[leading!, const SizedBox(width: 4)],
              Flexible(child: child),
            ],
          ),
        ),
      ),
    );
  }
}