keyboard_actions_bar 2.0.0 copy "keyboard_actions_bar: ^2.0.0" to clipboard
keyboard_actions_bar: ^2.0.0 copied to clipboard

A fully customizable keyboard toolbar for Flutter with zero FocusNode boilerplate, Material 3 theming, tap-outside dismiss, and 6 built-in actions.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:keyboard_actions_bar/keyboard_actions_bar.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'keyboard_actions_bar',
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        ),
        darkTheme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.indigo,
            brightness: Brightness.dark,
          ),
        ),
        themeMode: ThemeMode.system, // follows the phone's light/dark setting
        home: const _HomePage(),
      );
}

// ── Home ──────────────────────────────────────────────────────────────────────

class _HomePage extends StatefulWidget {
  const _HomePage();
  @override
  State<_HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<_HomePage> {
  final _done = TextEditingController();
  final _nav1 = TextEditingController();
  final _nav2 = TextEditingController();
  final _nav3 = TextEditingController();
  final _clear = TextEditingController();
  final _insertAt = TextEditingController();
  final _customLbl = TextEditingController();
  final _customIcon = TextEditingController();
  final _customBldr = TextEditingController();
  final _counter = TextEditingController();
  final _chips = TextEditingController();
  final _enabledDemo = TextEditingController();
  final _disabledDemo = TextEditingController();
  final _precedence = TextEditingController();
  final _autoName = TextEditingController();
  final _autoEmail = TextEditingController();

  @override
  void dispose() {
    for (final c in [
      _done,
      _nav1,
      _nav2,
      _nav3,
      _clear,
      _insertAt,
      _customLbl,
      _customIcon,
      _customBldr,
      _counter,
      _chips,
      _enabledDemo,
      _disabledDemo,
      _precedence,
      _autoName,
      _autoEmail,
    ]) {
      c.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text('keyboard_actions_bar'),
          backgroundColor: Theme.of(context).colorScheme.primaryContainer,
        ),
        body: KeyboardActionsBar(
          config: KeyboardConfig.allPlatforms,
          child: ListView(
            padding: const EdgeInsets.all(16),
            children: [
              // ── 1. done() ─────────────────────────────────────────────
              _Sep('1. KeyboardAction.done()'),
              KeyboardField(
                actions: [KeyboardAction.done()],
                child: _tf(_done, 'done — closes the keyboard'),
              ),

              // ── 2. next() / prev() ────────────────────────────────────
              _Sep('2. KeyboardAction.next() / prev()'),
              KeyboardField(
                actions: [KeyboardAction.next(), KeyboardAction.done()],
                child: _tf(_nav1, 'nav 1 — next only'),
              ),
              const SizedBox(height: 10),
              KeyboardField(
                actions: [
                  KeyboardAction.prev(),
                  KeyboardAction.next(),
                  KeyboardAction.done(),
                ],
                child: _tf(_nav2, 'nav 2 — prev + next'),
              ),
              const SizedBox(height: 10),
              KeyboardField(
                actions: [KeyboardAction.prev(), KeyboardAction.done()],
                child: _tf(_nav3, 'nav 3 — prev only'),
              ),

              // ── 3. clear(controller) ──────────────────────────────────
              _Sep('3. KeyboardAction.clear(controller)'),
              KeyboardField(
                actions: [
                  KeyboardAction.clear(_clear),
                  KeyboardAction.done(),
                ],
                child: _tf(_clear, 'clear — tap ⌫ to wipe field'),
              ),

              // ── 4. insertAt(controller) ───────────────────────────────
              _Sep('4. KeyboardAction.insertAt(controller)'),
              KeyboardField(
                actions: [
                  KeyboardAction.insertAt(_insertAt),
                  KeyboardAction.insertAt(_insertAt,
                      char: '.com', label: '.com'),
                  KeyboardAction.done(),
                ],
                child: _tf(_insertAt, 'insertAt — "@" or ".com"',
                    type: TextInputType.emailAddress),
              ),

              // ── 5. custom(label:) ─────────────────────────────────────
              _Sep('5. KeyboardAction.custom(label:)'),
              KeyboardField(
                actions: [
                  KeyboardAction.custom(
                    label: '#tag',
                    onTap: () {
                      _customLbl.text += '#tag ';
                      _customLbl.selection = TextSelection.collapsed(
                          offset: _customLbl.text.length);
                    },
                  ),
                  KeyboardAction.done(),
                ],
                child: _tf(_customLbl, 'custom label — tap "#tag"'),
              ),

              // ── 6. custom(icon:) ──────────────────────────────────────
              _Sep('6. KeyboardAction.custom(icon:)'),
              KeyboardField(
                actions: [
                  KeyboardAction.custom(
                    icon: Icons.emoji_emotions_outlined,
                    onTap: () {
                      _customIcon.text += '😊';
                      _customIcon.selection = TextSelection.collapsed(
                          offset: _customIcon.text.length);
                    },
                  ),
                  KeyboardAction.custom(
                    icon: Icons.send_outlined,
                    onTap: () {
                      FocusScope.of(context).unfocus();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Sent: "${_customIcon.text}"')),
                      );
                    },
                  ),
                  KeyboardAction.done(),
                ],
                child: _tf(_customIcon, 'custom icon — 😊 emoji / ✈ send'),
              ),

              // ── 7. custom(builder:) ───────────────────────────────────
              _Sep('7. KeyboardAction.custom(builder:)'),
              KeyboardField(
                actions: [
                  KeyboardAction.custom(
                    onTap: () {},
                    builder: (ctx, node) => GestureDetector(
                      onTap: () {
                        node.unfocus();
                        ScaffoldMessenger.of(ctx).showSnackBar(
                          SnackBar(
                              content:
                                  Text('Submitted: "${_customBldr.text}"')),
                        );
                      },
                      child: Container(
                        margin: const EdgeInsets.symmetric(
                            horizontal: 8, vertical: 6),
                        padding: const EdgeInsets.symmetric(
                            horizontal: 16, vertical: 6),
                        decoration: BoxDecoration(
                          color: Theme.of(ctx).colorScheme.primary,
                          borderRadius: BorderRadius.circular(20),
                        ),
                        child: Text(
                          'Submit',
                          style: TextStyle(
                            color: Theme.of(ctx).colorScheme.onPrimary,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
                child: _tf(_customBldr, 'custom builder — full widget button'),
              ),

              // ── 8. live character counter ─────────────────────────────
              _Sep('8. footer — live character counter'),
              KeyboardField(
                actions: [KeyboardAction.done()],
                footer: ValueListenableBuilder<TextEditingValue>(
                  valueListenable: _counter,
                  builder: (ctx, val, _) {
                    final n = val.text.length;
                    return Container(
                      width: double.infinity,
                      color: Theme.of(ctx)
                          .colorScheme
                          .surfaceContainerHighest
                          .withValues(alpha: .5),
                      padding: const EdgeInsets.symmetric(
                          horizontal: 16, vertical: 6),
                      child: Text(
                        '$n / 80',
                        textAlign: TextAlign.end,
                        style: TextStyle(
                          fontSize: 12,
                          color: n > 60
                              ? Theme.of(ctx).colorScheme.error
                              : Theme.of(ctx).colorScheme.onSurface,
                        ),
                      ),
                    );
                  },
                ),
                child: TextField(
                  controller: _counter,
                  maxLength: 80,
                  maxLines: 2,
                  decoration: const InputDecoration(
                    labelText: 'counter (80 chars max)',
                    border: OutlineInputBorder(),
                    alignLabelWithHint: true,
                    counterText: '',
                  ),
                ),
              ),

              // ── 9. suggestion chips ───────────────────────────────────
              _Sep('9. footer — suggestion chips'),
              KeyboardField(
                actions: [KeyboardAction.done()],
                footer: Builder(
                  builder: (ctx) => Container(
                    color: Theme.of(ctx)
                        .colorScheme
                        .surfaceContainerHighest
                        .withValues(alpha: .5),
                    padding:
                        const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
                    child: SingleChildScrollView(
                      scrollDirection: Axis.horizontal,
                      child: Row(
                        children: [
                          'flutter',
                          'dart',
                          'mobile',
                          'ios',
                          'android'
                        ]
                            .map((t) => Padding(
                                  padding: const EdgeInsets.only(right: 6),
                                  child: ActionChip(
                                    label: Text('#$t'),
                                    visualDensity: VisualDensity.compact,
                                    onPressed: () {
                                      final s = _chips.text;
                                      final v = s.isEmpty ? '#$t' : '$s #$t';
                                      _chips
                                        ..text = v
                                        ..selection = TextSelection.collapsed(
                                            offset: v.length);
                                    },
                                  ),
                                ))
                            .toList(),
                      ),
                    ),
                  ),
                ),
                child: _tf(_chips, 'chips — tap to append'),
              ),

              // ── 10. enabled: false ────────────────────────────────────
              _Sep('10. KeyboardField(enabled: false)'),
              KeyboardField(
                actions: [KeyboardAction.done()],
                child: _tf(_enabledDemo, 'enabled — normal toolbar'),
              ),
              const SizedBox(height: 10),
              KeyboardField(
                enabled: false,
                child: _tf(_disabledDemo, 'enabled: false — no toolbar shown'),
              ),

              // ── 11. Done label precedence ─────────────────────────────
              _Sep('11. per-field label overrides config default'),
              KeyboardField(
                actions: [KeyboardAction.done(label: 'Save')],
                child: _tf(_precedence,
                    'this field\'s Done button says "Save" instead'),
              ),

              // ── 12. Auto-discovery — no KeyboardField wrapper ─────────
              _Sep('12. auto-discovery — bare TextField, no wrapper'),
              _tf(_autoName, 'auto — name (bare TextField)'),
              const SizedBox(height: 10),
              _tf(_autoEmail, 'auto — email (bare TextField, last field)',
                  type: TextInputType.emailAddress),

              const SizedBox(height: 48),
            ],
          ),
        ),
      );
}

// ── Helpers ───────────────────────────────────────────────────────────────────

TextField _tf(
  TextEditingController ctrl,
  String label, {
  TextInputType? type,
}) =>
    TextField(
      controller: ctrl,
      keyboardType: type,
      decoration: InputDecoration(
        labelText: label,
        border: const OutlineInputBorder(),
      ),
    );

class _Sep extends StatelessWidget {
  final String label;
  const _Sep(this.label);
  @override
  Widget build(BuildContext context) => Padding(
        padding: const EdgeInsets.only(top: 28, bottom: 8),
        child: Row(children: [
          Expanded(
              child:
                  Divider(color: Theme.of(context).colorScheme.outlineVariant)),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 10),
            child: Text(
              label,
              style: Theme.of(context).textTheme.labelSmall?.copyWith(
                    color: Theme.of(context).colorScheme.primary,
                    fontWeight: FontWeight.bold,
                    letterSpacing: 1.1,
                  ),
            ),
          ),
          Expanded(
              child:
                  Divider(color: Theme.of(context).colorScheme.outlineVariant)),
        ]),
      );
}
3
likes
160
points
108
downloads
screenshot

Documentation

API reference

Publisher

verified publisherheshanpravintha.com

Weekly Downloads

A fully customizable keyboard toolbar for Flutter with zero FocusNode boilerplate, Material 3 theming, tap-outside dismiss, and 6 built-in actions.

Repository (GitHub)
View/report issues

Topics

#keyboard #form #toolbar #input #material

License

MIT (license)

Dependencies

flutter

More

Packages that depend on keyboard_actions_bar