keyboard_safe 1.0.0
keyboard_safe: ^1.0.0 copied to clipboard
A lightweight Flutter widget that prevents keyboard overflow by automatically adjusting layout, scrolling input fields into view, and providing sticky footers above the keyboard.
π± keyboard_safe #
A comprehensive Flutter widget that eliminates keyboard overflow issues with intelligent layout management, auto-scrolling, and smooth animations.
π Why KeyboardSafe? #
Keyboard handling is one of Flutter's biggest pain points. KeyboardSafe
transforms this challenge into a seamless experience with a single, powerful wrapper that provides:
- β Smart Layout Management: Automatically adjusts padding when keyboard appears/disappears
- π― Auto-Scroll Intelligence: Focused input fields scroll into view automatically
- π Flexible Footer Support: Choose between sticky footers or screen-bottom placement
- π Tap-to-Dismiss: Native-like keyboard dismissal when tapping outside
- π‘οΈ SafeArea Integration: Optional SafeArea wrapping for notched devices
- π¬ Buttery Smooth Animations: Configurable duration and curves for all transitions
- π± UX-First Design: Built with mobile app best practices in mind
Perfect for forms, chat interfaces, login screens, and any app with text input!
π Quick Start #
Installation #
Add to your pubspec.yaml
:
dependencies:
keyboard_safe: ^1.0.0
Then run:
flutter pub get
Basic Usage #
Transform your forms in seconds:
import 'package:keyboard_safe/keyboard_safe.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
body: KeyboardSafe(
scroll: true,
dismissOnTapOutside: true,
footer: ElevatedButton(
onPressed: _submitForm,
child: const Text('Submit'),
),
child: Column(
children: [
TextField(decoration: InputDecoration(labelText: 'Email')),
SizedBox(height: 16),
TextField(decoration: InputDecoration(labelText: 'Password')),
],
),
),
);
}
That's it! Your form now handles keyboard overflow, auto-scrolls to focused fields, and includes a responsive footer.
π― Complete Example #
Here's a production-ready form showcasing all features:
import 'package:flutter/material.dart';
import 'package:keyboard_safe/keyboard_safe.dart';
class LoginScreen extends StatefulWidget {
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
bool _isKeyboardVisible = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: KeyboardSafe(
// Enable scrolling for better UX
scroll: true,
// Auto-scroll focused fields into view
autoScrollToFocused: true,
// Tap anywhere to dismiss keyboard
dismissOnTapOutside: true,
// Respect device safe areas
safeArea: true,
// Add consistent padding
padding: EdgeInsets.all(24),
// Customize animation timing
keyboardAnimationDuration: Duration(milliseconds: 300),
keyboardAnimationCurve: Curves.easeInOut,
// Track keyboard state changes
onKeyboardChanged: (visible, height) {
setState(() => _isKeyboardVisible = visible);
print('Keyboard ${visible ? 'shown' : 'hidden'}: ${height}px');
},
// Footer stays at screen bottom (recommended UX)
footer: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 16),
child: ElevatedButton(
onPressed: () {
// Dismiss keyboard before processing
KeyboardSafe.dismissKeyboard(context);
_handleLogin();
},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
_isKeyboardVisible ? 'Login' : 'Login to Continue',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
),
),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Logo or header
Container(
height: 120,
margin: EdgeInsets.only(bottom: 32),
child: Icon(
Icons.lock_outline,
size: 64,
color: Theme.of(context).primaryColor,
),
),
// Email field
TextFormField(
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: 'Email Address',
hintText: 'you@example.com',
prefixIcon: Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your email';
}
return null;
},
),
SizedBox(height: 16),
// Password field
TextFormField(
obscureText: true,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
labelText: 'Password',
hintText: 'Enter your password',
prefixIcon: Icon(Icons.lock_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Please enter your password';
}
return null;
},
),
SizedBox(height: 24),
// Additional options
Row(
children: [
Icon(Icons.info_outline, size: 16, color: Colors.grey),
SizedBox(width: 8),
Expanded(
child: Text(
'Forgot password? Tap here to reset',
style: TextStyle(color: Colors.grey[600]),
),
),
],
),
SizedBox(height: 20), // Extra space before footer
],
),
),
),
);
}
void _handleLogin() {
if (_formKey.currentState?.validate() ?? false) {
// Process login
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login successful!')),
);
}
}
}
π¦ API Reference #
KeyboardSafe Parameters #
Parameter | Type | Default | Description |
---|---|---|---|
child |
Widget |
required | Main content inside the wrapper |
footer |
Widget? |
null |
Optional footer (e.g. Submit button) shown above keyboard |
scroll |
bool |
false |
Wraps content in SingleChildScrollView for tall layouts |
autoScrollToFocused |
bool |
true |
Auto-scrolls focused input fields into view |
dismissOnTapOutside |
bool |
false |
Dismisses keyboard when tapping outside input fields |
safeArea |
bool |
false |
Wraps layout in SafeArea widget |
persistFooter |
bool |
false |
If true , footer floats above keyboard (can feel intrusive) |
padding |
EdgeInsets |
EdgeInsets.zero |
Additional padding around content |
reverse |
bool |
false |
Reverses scroll direction (useful for chat interfaces) |
onKeyboardChanged |
Function(bool, double)? |
null |
Callback when keyboard visibility changes |
keyboardAnimationDuration |
Duration |
250ms |
Duration of keyboard-related animations |
keyboardAnimationCurve |
Curve |
Curves.easeOut |
Animation curve for smooth transitions |
Static Methods #
// Dismiss keyboard programmatically
KeyboardSafe.dismissKeyboard(context);
π¨ Usage Patterns #
1. Simple Forms #
KeyboardSafe(
scroll: true,
child: YourFormContent(),
)
2. Forms with Submit Button #
KeyboardSafe(
scroll: true,
footer: SubmitButton(),
child: YourFormContent(),
)
3. Chat Interface #
KeyboardSafe(
scroll: true,
reverse: true, // New messages at bottom
persistFooter: true, // Keep input visible
footer: MessageInput(),
child: MessageList(),
)
4. Full-Screen Experience #
KeyboardSafe(
safeArea: true,
dismissOnTapOutside: true,
padding: EdgeInsets.all(16),
child: YourContent(),
)
π Footer Behavior Guide #
persistFooter: false
(Recommended)
- Footer stays at screen bottom
- Provides familiar mobile app experience
- Better for submit buttons and navigation
persistFooter: true
(Use Sparingly)
- Footer floats above keyboard
- Can feel intrusive in most contexts
- Good for chat apps where input must stay visible
π¬ Demo #
See the dramatic difference KeyboardSafe makes:
π± Side-by-Side Comparison #
Without KeyboardSafe | With KeyboardSafe |
---|---|
![]() |
![]() |
β Footer covers form fields β No auto-scroll β Manual keyboard handling β Broken user experience |
β
Smart layout management β Auto-scroll to focused fields β Tap-to-dismiss keyboard β Seamless user experience |
π― The Problem vs Solution #
The Problem (Left): Typical Flutter keyboard issues that frustrate users:
- Footer floats up and covers input fields
- No automatic scrolling to focused fields
- Users must manually dismiss keyboard
- Broken layouts and poor UX
The Solution (Right): KeyboardSafe transforms the experience:
- Intelligent layout adjustment
- Automatic field focusing and scrolling
- Native-like tap-to-dismiss behavior
- Professional, polished user experience
π§ͺ Try it Live #
Experience the difference yourself:
Run Example App #
cd example
flutter run
Run DartPad Demo Locally #
cd example
flutter run -t lib/dartpad_demo.dart
π§ͺ Testing #
KeyboardSafe includes comprehensive widget tests:
flutter test
Test Coverage:
- β Keyboard padding application
- β Auto-scroll to focused fields
- β Footer positioning behavior
- β Tap-outside dismissal
- β Animation timing
- β SafeArea integration
π Migration Guide #
From v0.0.x to v1.0.0 #
v1.0.0 introduces powerful new features while maintaining backward compatibility:
β
No Breaking Changes: Existing code continues to work
π New Features: Enhanced footer control, better animations, improved UX
π Better Docs: Comprehensive examples and usage patterns
Recommended Updates:
// Before (still works)
KeyboardSafe(child: MyForm())
// After (recommended)
KeyboardSafe(
scroll: true,
dismissOnTapOutside: true,
footer: MySubmitButton(),
child: MyForm(),
)
π€ Contributing #
We welcome contributions! Here's how you can help:
- π Report Issues: Found a bug? Open an issue
- π‘ Suggest Features: Have ideas? We'd love to hear them
- π§ Submit PRs: Improvements and fixes are always welcome
- β Star the Repo: Help others discover this package
Development Setup #
git clone https://github.com/ChathraNavoda/keyboard_safe.git
cd keyboard_safe
flutter pub get
flutter test
π Package Stats #
- π― 100% Dart: Pure Flutter implementation
- π± All Platforms: iOS, Android, Web, Desktop
- π§ͺ Well Tested: Comprehensive test suite
- π Fully Documented: Every API has detailed docs
- π Production Ready: Used in real-world apps
π License #
MIT License - see LICENSE for details.
πͺ Built by the Community #
KeyboardSafe started as a solution to a common Flutter problem and evolved into a comprehensive package thanks to community feedback and real-world usage.
Made by @ChathraNavoda
If KeyboardSafe saved you time and headaches, consider starring β the repo to help others discover it!