ashlarui

Kbd

A keyboard key, for showing shortcuts.

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

import 'ui/kbd.dart';
import 'ui/theme.dart';

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

  @override
  Widget build(BuildContext context) {
    final t = AshlarTheme.of(context);
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Kbd.group(const [Kbd(child: Text('⌘')), Kbd(child: Text('⇧')), Kbd(child: Text('K'))]),
        const SizedBox(height: 16),
        Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Press ', style: TextStyle(color: t.colors.mutedForeground)),
            const Kbd(child: Text('Esc')),
            Text(' to close.', style: TextStyle(color: t.colors.mutedForeground)),
          ],
        ),
      ],
    );
  }
}

Try its options

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

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

Add it

In your Flutter project:

$ ashlar add kbd

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

Examples

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

Combinations

Keys pressed together.

Combinations, as it looks in an app Combinations, as it looks in an app, dark
// Combinations: Keys pressed together.
import 'package:flutter/widgets.dart';

import 'ui/kbd.dart';

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

  @override
  Widget build(BuildContext context) => const Wrap(
    spacing: 4,
    crossAxisAlignment: WrapCrossAlignment.center,
    children: [
      Kbd(child: Text('⌘')),
      Kbd(child: Text('Shift')),
      Kbd(child: Text('P')),
      SizedBox(width: 12),
      Text('Command palette'),
    ],
  );
}

API

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

Kbdclass

A key on the keyboard, for showing a shortcut. Kbd.group sets several side by side.

Parameter Type Default What it is for
childrequired Widget The key's name or symbol: Text('⌘'), Text('Enter').

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/kbd.dart 60 lines
// Ashlar UI: Kbd. Copied into your app by `ashlar add kbd`; it is yours to change.

import 'package:flutter/widgets.dart';

import 'theme.dart';

/// A key on the keyboard, for showing a shortcut. [Kbd.group] sets several side by side.
///
/// ```dart
/// Kbd(child: Text('Esc'))
/// Kbd.group([Kbd(child: Text('⌘')), Kbd(child: Text('K'))])
/// ```
class Kbd extends StatelessWidget {
  const Kbd({super.key, required this.child});

  /// The key's name or symbol: Text('⌘'), Text('Enter').
  final Widget child;

  /// Keys pressed together, with a small gap between them.
  static Widget group(List<Widget> keys) => Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      for (final (i, k) in keys.indexed) ...[
        if (i > 0) const SizedBox(width: 4),
        k,
      ],
    ],
  );

  @override
  Widget build(BuildContext context) {
    final t = AshlarTheme.of(context);
    return Container(
      // A height at least: larger text makes it taller rather than being cut off.
      constraints: const BoxConstraints(minWidth: 20, minHeight: 20),
      padding: const EdgeInsets.symmetric(horizontal: 4),
      decoration: BoxDecoration(
        color: t.colors.muted,
        borderRadius: BorderRadius.circular(t.radiusSm),
      ),
      child: DefaultTextStyle.merge(
        style: t.text.xs.copyWith(
          color: t.colors.mutedForeground,
          fontWeight: FontWeight.w500,
          height: 1,
          // The font's spare leading split above and below, so the text sits centred.
          leadingDistribution: TextLeadingDistribution.even,
        ),
        maxLines: 1,
        child: Align(
          widthFactor: 1,
          child: IconTheme.merge(
            data: IconThemeData(color: t.colors.mutedForeground, size: 12),
            child: child,
          ),
        ),
      ),
    );
  }
}