keycaps 0.1.1
keycaps: ^0.1.1 copied to clipboard
Keycap and keyboard-shortcut widgets for Flutter. Draws ⌘⇧P or Ctrl+Shift+P from a string, a LogicalKeyboardKey or a ShortcutActivator, themed and labelled per platform.
keycaps #
Keycaps and keyboard shortcuts as Flutter widgets.
Write the shortcut once; the package prints it the way the platform does —
⌘⇧P on macOS, Ctrl+Shift+P on Windows, Super instead of Win on Linux.
import 'package:keycaps/keycaps.dart';
const Keycap('K'); // a single cap
Keycaps.parse('Cmd+Shift+P'); // a chord
KeycapSequence.parse(const <String>['Ctrl+K', 'Ctrl+S']); // chords in order
The same list, three platforms #
Nothing below changed but the platform the caps resolve against — same strings,
same widgets. Apple sets modifiers solid as glyphs; Windows and Linux spell them
out, join them with +, and wear their own logo on the meta key.
| macOS | Windows | Linux |
|---|---|---|
![]() |
![]() |
![]() |
Both screenshots came out of the example app's platform switcher, on one machine.
Sources for a shortcut #
| Source | Widget |
|---|---|
| Text | Keycaps.parse('Cmd+Shift+P') |
| A Flutter activator | Keycaps.activator(const SingleActivator(LogicalKeyboardKey.keyP, meta: true)) |
| Explicit tokens | Keycaps(const <KeyToken>[KeyToken.key(LogicalKeyboardKey.meta), KeyToken.text('Fn')]) |
| A single key | const Keycap.logical(LogicalKeyboardKey.escape) |
| Anything else | Keycap.child(child: Icon(Icons.keyboard_return)) |
Keycaps.activator also orders the modifiers conventionally: ⌃⌥⇧ then ⌘ on
Apple, meta first on Windows and Linux.
Parsing rules #
Parts are separated by + and trimmed, so Ctrl + K and Ctrl+K are the same.
A + in key position is the plus key, so Cmd++ is two caps. Names are matched
case-insensitively against ShortcutParser.aliases (cmd, command, ⌘,
opt, esc, pgdn, f7, …); a single unknown character becomes that key, and
a longer unknown word is printed verbatim — handy for Fn.
Icons #
Some keys say more as a picture: the meta key wears the OS logo, arrows point.
KeycapIcons decides how far that goes.
| Mode | Draws |
|---|---|
auto (default) |
Windows/Linux logo on meta, plus the four arrow keys. Apple keeps its native glyphs — ⌘ is the convention there. |
always |
Every key with an icon: ⌘ ⌥ ⌃ ⇧ ⇪ ⏎ ⌫ ⇥, Home/End, PgUp/PgDn |
never |
Text only |
Keycaps.parse('Cmd+Shift+P', style: const KeycapStyle(icons: KeycapIcons.always))
Icons come from phosphor_icons, referenced only through PhosphorIconsRegular
static const fields so --tree-shake-icons can subset the font. Release
builds should pass that flag.
The other five Phosphor weights are a subtler cost: the shaker only subsets a
font it found references for, so weights nothing draws are copied whole —
about 2 MB. Mount const KeycapIconFontGuard() anywhere in your tree to
reference one glyph from each and cut them to a few hundred bytes apiece. Skip
it if your app already draws from those weights.
Pressing and highlighting #
Caps are not static pictures.
- Tap — a cap sinks under the finger and rises on release, whether or not
you pass a handler.
Keycap(onTap:)for one cap,Keycaps(onKeyTap:)for a row, which hands you theKeyTokenthat was hit. Turn the animation off withtapEffect: false. - Live highlight — a cap built from a
LogicalKeyboardKeylights up inhighlightColorwhile that key is actually held on the device, so a shortcut list doubles as a cheat sheet that answers as you type. Off withliveHighlight: false. - Forced —
Keycap(pressed: true)holds a cap down regardless of input.
Highlighting shares one app-wide HardwareKeyboard handler
(KeycapKeyboardWatcher), installed only while a highlighting cap is mounted
and never swallowing an event, so your own shortcuts still fire. Left/right
variants count: a cap for LogicalKeyboardKey.shift lights up for shiftLeft.
Styling #
Appearance comes from the nearest KeycapTheme, falling back to one derived
from the ambient Material theme. Style a whole subtree:
KeycapTheme(
data: KeycapThemeData.fromTheme(Theme.of(context), size: KeycapSize.small)
.copyWith(variant: KeycapVariant.outlined),
child: child,
)
…or one widget, with a KeycapStyle (all fields optional, applied over the
inherited theme):
Keycaps.parse(
'Cmd+K',
style: const KeycapStyle(size: KeycapSize.large, variant: KeycapVariant.flat),
)
| Knob | Values |
|---|---|
variant |
raised (default, with a bottom edge), flat, outlined, bare |
size |
KeycapSize.small / medium / large, or set metrics individually |
join |
auto (+ off Apple, nothing on Apple), plus, none, tight |
platform |
Force KeycapPlatform.apple / windows / linux labels |
icons |
auto, always, never (see above), plus iconSize |
liveHighlight / tapEffect |
Turn the two reactions off |
KeycapThemeData also exposes faceColor, edgeColor, borderColor,
foregroundColor, highlightColor, highlightForegroundColor, height,
minWidth, padding, radius, depth, borderWidth, spacing,
pressDuration, textStyle and separatorStyle, plus lerp for animating
between themes.
Accessibility #
Printed labels are glyphs (⌘, ⌫) or clipped words (PgDn), which read badly
aloud. Caps announce full words instead — a Keycaps.parse('Cmd+Shift+P') row
reads as “Command Shift P”. Override with Keycap(semanticLabel: …).
Example #
cd example
flutter run
A gallery of every variant, size and icon mode, a platform switcher, tap feedback, and a live keyboard mirror.



