Skip to main content

Interactive Components

Interactive components focus on user interaction and state management. They handle user input, provide feedback, and facilitate complex interactions throughout the BuildAppsWith platform.

Core Interactive Components

Interactive components are organized into several categories based on their functionality:

Form Components

Form components are used to capture user input and manage form state:
ComponentPurposeVariantsStatus
InputText entryText, Number, Email, PasswordCompleted
SelectOption selectionDropdown, Multi-select, SearchableCompleted
CheckboxBoolean selectionStandard, Toggle, CardCompleted
RadioSingle selectionStandard, Card, Button GroupCompleted
DatePickerDate selectionSingle, Range, CalendarIn Progress
FileUploadFile attachmentDrop Zone, Button, PreviewIn Progress
FormComplete form with validationStandard, Multi-step, WizardCompleted
All form components are integrated with React Hook Form and Zod for validation.
// Example Form with validation
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Form, Input, Button } from '@/components/ui/form';

// Define schema with Zod
const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8)
});

function LoginForm() {
  const form = useForm({
    resolver: zodResolver(schema)
  });
  
  return (
    <Form form={form} onSubmit={values => console.log(values)}>
      <Input name="email" label="Email" />
      <Input name="password" label="Password" type="password" />
      <Button type="submit">Log In</Button>
    </Form>
  );
}
Navigation components help users move between different sections of the application:
ComponentPurposeVariantsStatus
TabsSection navigationHorizontal, Vertical, UnderlinedCompleted
MenuOption selectionDropdown, Hamburger, SidebarCompleted
PaginationPage navigationNumbers, Prev/Next, InfiniteCompleted
BreadcrumbsLocation trackingStandard, CollapsedCompleted
StepperMulti-step processHorizontal, Vertical, NumberedIn Progress
Nav LinkNavigation linkStandard, Active, IconCompleted
// Example Tabs Component
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';

function ProfileTabs() {
  return (
    <Tabs defaultValue="portfolio">
      <TabsList>
        <TabsTrigger value="portfolio">Portfolio</TabsTrigger>
        <TabsTrigger value="reviews">Reviews</TabsTrigger>
        <TabsTrigger value="availability">Availability</TabsTrigger>
      </TabsList>
      
      <TabsContent value="portfolio">
        {/* Portfolio content */}
      </TabsContent>
      
      <TabsContent value="reviews">
        {/* Reviews content */}
      </TabsContent>
      
      <TabsContent value="availability">
        {/* Availability content */}
      </TabsContent>
    </Tabs>
  );
}

Overlay Components

Overlay components display content above the main interface:
ComponentPurposeVariantsStatus
ModalFocused user interactionStandard, Large, ConfirmationCompleted
DialogConfirmation or infoAlert, Confirmation, FormCompleted
DrawerSide panel contentLeft, Right, BottomCompleted
PopoverContextual informationTooltip, Menu, InfoCompleted
ToastTemporary notificationsSuccess, Error, Info, WarningCompleted
CommandPaletteKeyboard command accessStandard, SearchIn Progress
Overlay components should be used sparingly and should not interrupt the user’s flow unnecessarily.
// Example Modal Component
import { Modal, ModalContent, ModalHeader, ModalFooter, Button } from '@/components/ui/modal';

function DeleteConfirmation() {
  const [open, setOpen] = useState(false);
  
  return (
    <>
      <Button variant="destructive" onClick={() => setOpen(true)}>
        Delete Account
      </Button>
      
      <Modal open={open} onOpenChange={setOpen}>
        <ModalHeader>Confirm Account Deletion</ModalHeader>
        <ModalContent>
          Are you sure you want to delete your account? This action cannot be undone.
        </ModalContent>
        <ModalFooter>
          <Button variant="ghost" onClick={() => setOpen(false)}>
            Cancel
          </Button>
          <Button variant="destructive" onClick={handleDelete}>
            Delete
          </Button>
        </ModalFooter>
      </Modal>
    </>
  );
}

Data Display Components

Components that display and interact with data:
ComponentPurposeVariantsStatus
TableStructured data displaySortable, Filterable, PaginatedCompleted
ListSequential data displayOrdered, Unordered, ActionCompleted
CalendarDate-based data displayMonth, Week, Day, AgendaIn Progress
ChartData visualizationBar, Line, Pie, AreaPlanned
DataGridComplex data manipulationEditable, Sortable, FilterablePlanned
TreeViewHierarchical dataExpandable, SelectableIn Progress

State Management

Interactive components follow consistent state management patterns:

Component State

Local component state is managed using React’s useState hook:
// Local component state
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={() => setCount(count + 1)}>Increment</Button>
    </div>
  );
}

Form State

Form state is managed using React Hook Form:
// Form state management
function ProfileForm() {
  const { control, handleSubmit, formState: { errors } } = useForm({
    defaultValues: {
      name: '',
      email: '',
      bio: ''
    }
  });
  
  const onSubmit = (data) => {
    // Handle form submission
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Form fields */}
    </form>
  );
}

Shared State

Shared state across components is managed using React Context:
// Context provider
export const FilterContext = createContext();

export function FilterProvider({ children }) {
  const [filters, setFilters] = useState({});
  
  return (
    <FilterContext.Provider value={{ filters, setFilters }}>
      {children}
    </FilterContext.Provider>
  );
}

// Context consumer
function FilterPanel() {
  const { filters, setFilters } = useContext(FilterContext);
  
  // Use shared filter state
}

Interaction Patterns

Loading States

Interactive components handle loading states:
// Loading state example
function SubmitButton({ isLoading }) {
  return (
    <Button disabled={isLoading}>
      {isLoading ? (
        <>
          <Spinner className="mr-2 h-4 w-4" />
          <span>Loading...</span>
        </>
      ) : (
        <span>Submit</span>
      )}
    </Button>
  );
}

Error Handling

Components handle errors gracefully:
// Error handling example
function SearchForm() {
  const [error, setError] = useState(null);
  
  const handleSearch = async (query) => {
    try {
      const results = await fetchSearchResults(query);
      // Handle results
    } catch (err) {
      setError('Search failed. Please try again.');
    }
  };
  
  return (
    <div>
      {error && (
        <Alert variant="destructive" className="mb-4">
          <AlertTitle>Error</AlertTitle>
          <AlertDescription>{error}</AlertDescription>
        </Alert>
      )}
      
      {/* Search form */}
    </div>
  );
}

Validation Feedback

Components provide immediate validation feedback:
// Validation feedback example
function EmailInput() {
  const [value, setValue] = useState('');
  const [error, setError] = useState('');
  
  const validateEmail = (email) => {
    if (!email) {
      setError('Email is required');
      return false;
    }
    
    if (!/\S+@\S+\.\S+/.test(email)) {
      setError('Invalid email format');
      return false;
    }
    
    setError('');
    return true;
  };
  
  return (
    <div>
      <Label htmlFor="email">Email</Label>
      <Input
        id="email"
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
          validateEmail(e.target.value);
        }}
        onBlur={() => validateEmail(value)}
        aria-invalid={Boolean(error)}
        aria-describedby={error ? "email-error" : undefined}
      />
      {error && (
        <p id="email-error" className="text-red-500 text-sm mt-1">
          {error}
        </p>
      )}
    </div>
  );
}

Accessibility

Interactive components have enhanced accessibility requirements:
  1. Keyboard Navigation: Fully keyboard accessible with logical tab order
  2. ARIA Attributes: Proper ARIA roles, states, and properties
  3. Focus Management: Proper focus trapping in modals and dialogs
  4. Live Regions: Announcements for dynamic content changes
  5. Error Identification: Clear error messages with proper associations
// Accessibility in dialogs example
function AccessibleDialog({ title, isOpen, onClose, children }) {
  // Return focus to trigger element when dialog closes
  const previousFocus = React.useRef(null);
  
  React.useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement;
    } else if (previousFocus.current) {
      previousFocus.current.focus();
    }
  }, [isOpen]);
  
  if (!isOpen) return null;
  
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="dialog-title"
      className="fixed inset-0 z-50 flex items-center justify-center"
    >
      <div className="fixed inset-0 bg-black bg-opacity-50" onClick={onClose} />
      <div className="bg-white rounded-lg p-6 z-10 max-w-md w-full">
        <h2 id="dialog-title" className="text-xl font-semibold mb-4">
          {title}
        </h2>
        <div>{children}</div>
        <button
          className="mt-4 px-4 py-2 bg-primary text-white rounded"
          onClick={onClose}
        >
          Close
        </button>
      </div>
    </div>
  );
}

Testing

Interactive components require thorough testing:
// Component test example
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click Me</Button>);
    
    fireEvent.click(screen.getByText('Click Me'));
    
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
  
  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Disabled</Button>);
    
    expect(screen.getByText('Disabled')).toBeDisabled();
  });
});

See Also