fab-actions

doitsu2014's avatarfrom doitsu2014

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.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 11, 2026

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.
namefab-actions
descriptionFloating 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:

  1. Cancel (top) - furthest from trigger
  2. Save (middle) - closer to trigger
  3. 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 transition
  • opacity-0 / opacity-100 - Fade effect
  • translate-y-4 / translate-y-0 - Slide up effect
  • pointer-events-none - Disable clicks when collapsed
  • rotate-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 submission
  • disabled state handled for loading
  • Keyboard accessible (Tab, Enter, Space)

Best Practices

  1. Keep the FAB trigger always visible
  2. Use consistent button sizes (btn-lg btn-circle)
  3. Use distinct colors for different actions
  4. Add shadows for depth (shadow-md, shadow-lg)
  5. Include loading states for submit buttons
  6. Close FAB after navigation actions
  7. Use icons without text for circle buttons