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:
- Use Radix UI to create clean and reusable UI components.
- Integrate Phosphor Icons to add icons for buttons, inputs, and navigation links.
- Style the TodoList app’s layout for improved user experience.
- 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-react4. 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.HouseandMagicWand: 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
- Start the backend server:
cd backend
npm run dev- Start the React frontend:
cd frontend
npm startOpen http://localhost:3000 in your browser.
8. Testing and Observing Changes
Verify:
- Navigation Bar: Styled with icons and links.
-
Todo List:
- Checkboxes mark tasks as completed.
- Delete buttons remove tasks.
- Loading states display when tasks are being added.
- AI Tools Page: Task description generation works.
9. Homework Assignment
- Add a Dialog Component from Radix UI for confirming task deletions.
- Style the app further using custom CSS or TailwindCSS.
- 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.
