Lesson 9: Styling the App with Radix UI and Phosphor Icons

Goal

Integrate Radix UI for accessible and customizable components and Phosphor Icons for enhanced visuals. We’ll style the TodoList app and navigation for better UI/UX.


1. Learning Objectives

By the end of this lesson, students will:

  1. Use Radix UI to create clean and reusable UI components.
  2. Integrate Phosphor Icons to add icons for buttons, inputs, and navigation links.
  3. Style the TodoList app’s layout for improved user experience.
  4. Add loading indicators and error states to API interactions.

2. Why Radix UI and Phosphor Icons?

Radix UI

Radix provides:

  • Accessible components out of the box.
  • Customizability while maintaining clean semantics.
  • Components for buttons, inputs, checkboxes, and dialogs.

Phosphor Icons

Phosphor is a flexible, modern icon library with lightweight icons.


3. Installing Radix UI and Phosphor Icons

Run the following command to install dependencies:

npm install @radix-ui/react-button @radix-ui/react-checkbox phosphor-react

4. Adding Navigation Styling with Radix UI and Icons

4.1. Update Navigation Bar

Modify the App.tsx file to include Radix UI Buttons and Phosphor Icons.

File: src/App.tsx

import React from 'react';
import { Link, Outlet } from '@tanstack/react-router';
import { House, MagicWand } from 'phosphor-react';
import * as Button from '@radix-ui/react-button';

const App: React.FC = () => {
  return (
    <div>
      <nav style={{ display: 'flex', gap: '1.5rem', padding: '1rem', borderBottom: '1px solid #ccc' }}>
        <Button.Root asChild>
          <Link to="/" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
            <House size={24} /> Home
          </Link>
        </Button.Root>
        <Button.Root asChild>
          <Link to="/ai-tools" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
            <MagicWand size={24} /> AI Tools
          </Link>
        </Button.Root>
      </nav>
      <main style={{ padding: '1.5rem' }}>
        <Outlet />
      </main>
    </div>
  );
};

export default App;

Key Notes:

  • Button.Root: Radix UI’s button component with asChild to wrap other elements.
  • House and MagicWand: Phosphor icons for navigation links.

5. Styling the Todo List with Checkboxes

We’ll use Radix UI Checkbox to mark tasks as completed.


5.1. Update TodoItem Component

File: src/components/TodoItem.tsx

import React from 'react';
import * as Checkbox from '@radix-ui/react-checkbox';
import { Check, Trash } from 'phosphor-react';

interface TodoItemProps {
  id: string;
  title: string;
  completed: boolean;
  onDelete: () => void;
  onToggle: () => void;
}

const TodoItem: React.FC<TodoItemProps> = ({ title, completed, onDelete, onToggle }) => {
  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        padding: '0.5rem 1rem',
        border: '1px solid #ddd',
        marginBottom: '0.5rem',
        borderRadius: '5px',
      }}
    >
      <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
        <Checkbox.Root checked={completed} onCheckedChange={onToggle} style={{ width: 20, height: 20 }}>
          <Checkbox.Indicator>
            <Check size={18} />
          </Checkbox.Indicator>
        </Checkbox.Root>
        <span style={{ textDecoration: completed ? 'line-through' : 'none' }}>{title}</span>
      </div>
      <button onClick={onDelete} style={{ background: 'none', border: 'none', cursor: 'pointer' }}>
        <Trash size={20} color="red" />
      </button>
    </div>
  );
};

export default TodoItem;

5.2. Add Toggle Functionality

Update the TodoList to toggle completed status.

File: src/components/TodoList.tsx

import React from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { fetchTodos, deleteTodo, createTodo } from '../api/todoApi';
import TodoItem from './TodoItem';

const TodoList: React.FC = () => {
  const queryClient = useQueryClient();

  const { data: todos = [], isLoading, error } = useQuery(['todos'], fetchTodos);

  const deleteMutation = useMutation(deleteTodo, {
    onSuccess: () => queryClient.invalidateQueries(['todos']),
  });

  const toggleMutation = useMutation(
    async (todo: any) => createTodo({ ...todo, completed: !todo.completed }),
    {
      onSuccess: () => queryClient.invalidateQueries(['todos']),
    }
  );

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Failed to load todos.</p>;

  return (
    <div>
      {todos.map((todo: any) => (
        <TodoItem
          key={todo._id}
          id={todo._id}
          title={todo.title}
          completed={todo.completed}
          onDelete={() => deleteMutation.mutate(todo._id)}
          onToggle={() => toggleMutation.mutate(todo)}
        />
      ))}
    </div>
  );
};

export default TodoList;

6. Add Loading Indicators for Buttons

Radix UI components don’t provide loaders out of the box, so we’ll add manual loading states.

Update the AddTodo component:

File: src/components/AddTodo.tsx

import React, { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createTodo } from '../api/todoApi';

const AddTodo: React.FC = () => {
  const [title, setTitle] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const queryClient = useQueryClient();

  const mutation = useMutation(createTodo, {
    onMutate: () => setIsLoading(true),
    onSuccess: () => {
      queryClient.invalidateQueries(['todos']);
      setTitle('');
    },
    onSettled: () => setIsLoading(false),
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (title) mutation.mutate(title);
  };

  return (
    <form onSubmit={handleSubmit} style={{ marginBottom: '1rem' }}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Add a new task"
        style={{ padding: '0.5rem', marginRight: '0.5rem' }}
      />
      <button type="submit" disabled={isLoading} style={{ padding: '0.5rem' }}>
        {isLoading ? 'Adding...' : 'Add Task'}
      </button>
    </form>
  );
};

export default AddTodo;

7. Running the Application

  1. Start the backend server:
cd backend
npm run dev
  1. Start the React frontend:
cd frontend
npm start

Open http://localhost:3000 in your browser.


8. Testing and Observing Changes

Verify:

  1. Navigation Bar: Styled with icons and links.
  2. Todo List:

    • Checkboxes mark tasks as completed.
    • Delete buttons remove tasks.
    • Loading states display when tasks are being added.
  3. AI Tools Page: Task description generation works.

9. Homework Assignment

  1. Add a Dialog Component from Radix UI for confirming task deletions.
  2. Style the app further using custom CSS or TailwindCSS.
  3. Add icons for additional actions, such as Edit.

Next Lesson Preview: Lesson 10

In Lesson 10, we will:

  • Discuss QA and testing the frontend with tools like React Testing Library and Jest.

Copyright © Big Poppa Code & Progress and Fortune LLC 2025