Skip to main content

Overview

NSFloatingActionButton is a specialized button component that renders as a portal to the document body, floating above all other content. It’s positioned at the bottom of the viewport and can be placed on the left, middle, or right side.

Key Features

Portal Rendering

Renders outside the normal component hierarchy using React portals

Flexible Positioning

Position on left, middle, or right side of the viewport

Fixed Placement

Stays visible at the bottom while scrolling

Custom Content

Accepts any React component as children

Import

import { NSFloatingActionButton } from '@newtonschool/grauity';

Basic Usage

import { NSFloatingActionButton, NSIconButton } from '@newtonschool/grauity';

function App() {
  return (
    <NSFloatingActionButton
      position="right"
      onClick={(triggerRef) => console.log('FAB clicked')}
    >
      <NSIconButton icon="plus" />
    </NSFloatingActionButton>
  );
}

Props

children
React.ReactNode
The element to be rendered as the floating button. Typically an NSIconButton or custom button component.
position
'left' | 'mid' | 'right'
default:"'right'"
The horizontal position of the floating button at the bottom of the viewport.
  • 'left': Positioned on the left side
  • 'mid': Centered horizontally
  • 'right': Positioned on the right side
onClick
(triggerRef: React.MutableRefObject<HTMLDivElement>) => void
Function called when the floating button is clicked. Receives a ref to the floating button element, which is useful for positioning popovers or menus relative to the button.
sideOffset
string
default:"'10px'"
The offset of the floating button from the side boundaries (left or right edge of the viewport).
bottomOffset
string
default:"'10px'"
The offset of the floating button from the bottom boundary of the viewport.
className
string
Additional CSS class names to apply to the floating button container.

Examples

Three Position Options

import { NSFloatingActionButton, NSIconButton } from '@newtonschool/grauity';

function PositionExamples() {
  return (
    <>
      {/* Left position */}
      <NSFloatingActionButton position="left">
        <NSIconButton icon="arrow-left" />
      </NSFloatingActionButton>

      {/* Center position */}
      <NSFloatingActionButton position="mid">
        <NSIconButton icon="plus" />
      </NSFloatingActionButton>

      {/* Right position (default) */}
      <NSFloatingActionButton position="right">
        <NSIconButton icon="arrow-right" />
      </NSFloatingActionButton>
    </>
  );
}

Custom Offsets

import { NSFloatingActionButton, NSIconButton } from '@newtonschool/grauity';

function CustomOffsets() {
  return (
    <NSFloatingActionButton
      position="right"
      sideOffset="40px"
      bottomOffset="80px"
    >
      <NSIconButton icon="plus" size="large" />
    </NSFloatingActionButton>
  );
}

With Menu

import { 
  NSFloatingActionButton, 
  NSIconButton, 
  NSPopOver,
  NSButton 
} from '@newtonschool/grauity';
import { useState, useRef } from 'react';
import styled from 'styled-components';

const MenuContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  min-width: 200px;
`;

function FABWithMenu() {
  const [isOpen, setIsOpen] = useState(false);
  const triggerRef = useRef(null);

  const handleOpen = (ref) => {
    triggerRef.current = ref.current;
    setIsOpen(true);
  };

  const handleAction = (action: string) => {
    console.log('Action:', action);
    setIsOpen(false);
  };

  return (
    <>
      <NSFloatingActionButton onClick={handleOpen}>
        <NSIconButton icon={isOpen ? 'x' : 'plus'} />
      </NSFloatingActionButton>

      <NSPopOver
        triggerRef={triggerRef}
        isOpen={isOpen}
        direction="top"
        onClose={() => setIsOpen(false)}
        shouldCloseOnOutsideClick
      >
        <MenuContainer>
          <NSButton 
            variant="ghost" 
            icon="file-plus"
            onClick={() => handleAction('new')}
          >
            New Document
          </NSButton>
          <NSButton 
            variant="ghost" 
            icon="upload"
            onClick={() => handleAction('upload')}
          >
            Upload File
          </NSButton>
          <NSButton 
            variant="ghost" 
            icon="folder"
            onClick={() => handleAction('folder')}
          >
            New Folder
          </NSButton>
        </MenuContainer>
      </NSPopOver>
    </>
  );
}

Custom Styled Button

import { NSFloatingActionButton } from '@newtonschool/grauity';
import styled from 'styled-components';

const CustomFAB = styled.button`
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border: none;
  color: white;
  font-size: 24px;
  cursor: pointer;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  transition: transform 0.2s;

  &:hover {
    transform: scale(1.1);
  }

  &:active {
    transform: scale(0.95);
  }
`;

function StyledFAB() {
  return (
    <NSFloatingActionButton
      position="right"
      onClick={() => console.log('Custom FAB clicked')}
    >
      <CustomFAB>+</CustomFAB>
    </NSFloatingActionButton>
  );
}

Multiple FABs

import { NSFloatingActionButton, NSIconButton } from '@newtonschool/grauity';

function MultipleFABs() {
  return (
    <>
      {/* Main action on the right */}
      <NSFloatingActionButton 
        position="right"
        onClick={() => console.log('Main action')}
      >
        <NSIconButton icon="plus" />
      </NSFloatingActionButton>

      {/* Secondary action on the left */}
      <NSFloatingActionButton 
        position="left"
        onClick={() => console.log('Secondary action')}
      >
        <NSIconButton icon="message" />
      </NSFloatingActionButton>
    </>
  );
}

Animated FAB

import { NSFloatingActionButton, NSIconButton } from '@newtonschool/grauity';
import { useState } from 'react';
import styled from 'styled-components';

const RotatingIcon = styled.div<{ $isOpen: boolean }>`
  transition: transform 0.3s ease;
  transform: rotate(${props => props.$isOpen ? '45deg' : '0deg'});
`;

function AnimatedFAB() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <NSFloatingActionButton
      onClick={() => setIsOpen(!isOpen)}
    >
      <RotatingIcon $isOpen={isOpen}>
        <NSIconButton icon="plus" />
      </RotatingIcon>
    </NSFloatingActionButton>
  );
}

Behavior

The component uses ReactDOM.createPortal to render the button as a direct child of document.body. This ensures it appears above all other content and is not affected by parent component positioning or overflow settings.
The onClick callback receives a ref to the floating button element. This is particularly useful when you need to position a popover or menu relative to the button, as shown in the examples with NSPopOver.

Positioning Details

  • Fixed positioning: The button uses position: fixed in CSS
  • Z-index: Rendered at a high z-index to appear above other content
  • Bottom alignment: Always positioned relative to the bottom of the viewport
  • Horizontal alignment: Controlled by the position prop
    • left: left: {sideOffset}
    • mid: Centered using transform
    • right: right: {sideOffset}

Use Cases

Use for the most important action on a page, such as creating a new item, composing a message, or starting a new task.
<NSFloatingActionButton onClick={handleCreateNew}>
  <NSIconButton icon="plus" />
</NSFloatingActionButton>
Combine with a popover to show a menu of quick actions:
<NSFloatingActionButton onClick={toggleMenu}>
  <NSIconButton icon="ellipsis-vertical" />
</NSFloatingActionButton>
Provide quick access to chat or support features:
<NSFloatingActionButton position="right">
  <NSIconButton icon="message-circle" />
</NSFloatingActionButton>
Create a scroll-to-top button for long pages:
<NSFloatingActionButton 
  onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
>
  <NSIconButton icon="arrow-up" />
</NSFloatingActionButton>

Best Practices

Do:
  • Use for primary or frequently used actions
  • Keep the button visible and accessible at all times
  • Use clear, recognizable icons
  • Provide appropriate spacing from screen edges
  • Consider mobile viewport sizes when setting offsets
Don’t:
  • Use multiple FABs for unrelated actions on the same screen
  • Block important content with the FAB placement
  • Use for destructive actions without confirmation
  • Make the FAB too small (minimum 48x48px for touch targets)

Accessibility

  • Ensure the child button element has appropriate ARIA labels
  • Use NSIconButton which includes proper accessibility attributes
  • Provide sufficient contrast for the button and icon
  • Ensure the button is keyboard accessible
  • Consider adding a tooltip for clarity
<NSFloatingActionButton onClick={handleClick}>
  <NSIconButton 
    icon="plus" 
    aria-label="Create new item"
  />
</NSFloatingActionButton>

Mobile Considerations

On mobile devices, ensure the FAB doesn’t interfere with native navigation elements or important content. Consider using larger offsets on smaller screens:
<NSFloatingActionButton
  sideOffset="16px"
  bottomOffset="80px" // Account for mobile bottom navigation
>
  <NSIconButton icon="plus" />
</NSFloatingActionButton>

Build docs developers (and LLMs) love