fab-actions
Floating Action Button (FAB) for form actions. Use when adding Save/Cancel buttons to forms that need to be accessible while scrolling through long content.
When & Why to Use This Skill
This Claude skill provides a ready-to-implement Floating Action Button (FAB) component specifically designed for React and Tailwind CSS environments. It solves the common UX challenge of maintaining access to primary form actions (like Save or Cancel) in long-scroll interfaces, ensuring that critical buttons remain within the user's reach at all times without obstructing content.
Use Cases
- Improving usability in long administrative or data-entry forms where users need to save progress without scrolling to the bottom of the page.
- Optimizing mobile-responsive web applications by placing primary actions in the 'thumb-zone' for better ergonomic interaction.
- Streamlining complex interfaces by nesting multiple secondary actions (e.g., Delete, Preview, Export) within a single, clean expandable trigger to reduce visual clutter.
| name | fab-actions |
|---|---|
| description | Floating Action Button (FAB) for form actions. Use when adding Save/Cancel buttons to forms that need to be accessible while scrolling through long content. |
FAB Actions
Overview
This skill provides a Floating Action Button (FAB) pattern for form action buttons (Save, Cancel, etc.). The FAB stays fixed at the bottom-right corner and expands on click to reveal action buttons.
Features
- Fixed position at bottom-right corner
- Click to expand/collapse
- Plus icon rotates 45° to become X when expanded
- Smooth animations with transitions
- Buttons are colinear (vertically aligned)
- Works with forms (submit button support)
Required Imports
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Plus, Save, X } from 'lucide-react';
State Setup
Add the FAB state to your component:
const [fabOpen, setFabOpen] = useState(false);
FAB Component Template
Place this at the end of your form, before the closing </form> tag:
{/* Action Buttons - FAB */}
<div className="fixed bottom-8 right-8 z-50 flex flex-col items-center gap-3">
{/* Expandable Buttons */}
<div className={`flex flex-col items-center gap-3 transition-all duration-300 ${fabOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4 pointer-events-none'}`}>
{/* Cancel Button */}
<button
type="button"
className="btn btn-lg btn-circle shadow-md bg-base-100 hover:bg-base-200"
onClick={() => {
navigate('/admin/your-route');
setFabOpen(false);
}}
disabled={isLoading}
>
<X className="w-5 h-5" />
</button>
{/* Save/Update Button */}
<button
type="submit"
className="btn btn-lg btn-circle btn-success shadow-md"
disabled={isLoading}
>
{isSubmitting ? (
<span className="loading loading-spinner loading-sm"></span>
) : (
<Save className="w-5 h-5" />
)}
</button>
</div>
{/* Main FAB Trigger */}
<button
type="button"
className={`btn btn-lg btn-circle btn-primary shadow-lg transition-transform duration-300 ${fabOpen ? 'rotate-45' : ''}`}
onClick={() => setFabOpen(!fabOpen)}
>
<Plus className="w-5 h-5" />
</button>
</div>
Customization
Button Order
Buttons appear from top to bottom in the expanded state:
- Cancel (top) - furthest from trigger
- Save (middle) - closer to trigger
- FAB Trigger (bottom) - always visible
Adding More Actions
Add buttons inside the expandable div, before the Cancel button:
<div className={`flex flex-col items-center gap-3 transition-all duration-300 ${fabOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4 pointer-events-none'}`}>
{/* Delete Button (optional) */}
<button
type="button"
className="btn btn-lg btn-circle btn-error shadow-md"
onClick={handleDelete}
disabled={isLoading}
>
<Trash className="w-5 h-5" />
</button>
{/* Cancel Button */}
<button ...>
<X className="w-5 h-5" />
</button>
{/* Save Button */}
<button ...>
<Save className="w-5 h-5" />
</button>
</div>
Button Variants
// Primary action (main trigger)
className="btn btn-lg btn-circle btn-primary shadow-lg"
// Success action (save/confirm)
className="btn btn-lg btn-circle btn-success shadow-md"
// Neutral action (cancel)
className="btn btn-lg btn-circle shadow-md bg-base-100 hover:bg-base-200"
// Danger action (delete)
className="btn btn-lg btn-circle btn-error shadow-md"
// Secondary action
className="btn btn-lg btn-circle btn-secondary shadow-md"
Icon Options
Common icons for FAB actions:
import {
Plus, // Main trigger
Save, // Save/Update
X, // Cancel/Close
Trash, // Delete
Check, // Confirm
Send, // Submit/Send
Download, // Download
Upload, // Upload
Edit, // Edit mode
Eye, // Preview
} from 'lucide-react';
Animation Details
Transition Classes
transition-all duration-300- Smooth 300ms transitionopacity-0/opacity-100- Fade effecttranslate-y-4/translate-y-0- Slide up effectpointer-events-none- Disable clicks when collapsedrotate-45- Rotates Plus to X shape
Customizing Animation Speed
// Faster (200ms)
className="transition-all duration-200"
// Slower (500ms)
className="transition-all duration-500"
Complete Example
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import { Plus, Save, X } from 'lucide-react';
export default function MyForm({ id }: { id?: string }) {
const navigate = useNavigate();
const [fabOpen, setFabOpen] = useState(false);
const {
handleSubmit,
formState: { isSubmitting },
} = useForm();
const isLoading = isSubmitting;
const onSubmit = async (data: FormData) => {
// Submit logic
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Form fields here */}
{/* FAB Actions */}
<div className="fixed bottom-8 right-8 z-50 flex flex-col items-center gap-3">
<div className={`flex flex-col items-center gap-3 transition-all duration-300 ${fabOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4 pointer-events-none'}`}>
<button
type="button"
className="btn btn-lg btn-circle shadow-md bg-base-100 hover:bg-base-200"
onClick={() => {
navigate('/admin/list');
setFabOpen(false);
}}
disabled={isLoading}
>
<X className="w-5 h-5" />
</button>
<button
type="submit"
className="btn btn-lg btn-circle btn-success shadow-md"
disabled={isLoading}
>
{isSubmitting ? (
<span className="loading loading-spinner loading-sm"></span>
) : (
<Save className="w-5 h-5" />
)}
</button>
</div>
<button
type="button"
className={`btn btn-lg btn-circle btn-primary shadow-lg transition-transform duration-300 ${fabOpen ? 'rotate-45' : ''}`}
onClick={() => setFabOpen(!fabOpen)}
>
<Plus className="w-5 h-5" />
</button>
</div>
</form>
);
}
Accessibility
- FAB uses semantic
<button>elements - Proper
type="button"for non-submit buttons type="submit"for form submissiondisabledstate handled for loading- Keyboard accessible (Tab, Enter, Space)
Best Practices
- Keep the FAB trigger always visible
- Use consistent button sizes (
btn-lg btn-circle) - Use distinct colors for different actions
- Add shadows for depth (
shadow-md,shadow-lg) - Include loading states for submit buttons
- Close FAB after navigation actions
- Use icons without text for circle buttons