0
# Feedback Components
1
2
NextUI provides comprehensive feedback components for communicating system status, user notifications, loading states, and contextual information to enhance user experience.
3
4
## Capabilities
5
6
### Alert
7
8
Notification component for displaying important messages, warnings, and status information with customizable styling and actions.
9
10
```typescript { .api }
11
interface AlertProps {
12
/** Alert content */
13
children?: React.ReactNode;
14
/** Alert title */
15
title?: React.ReactNode;
16
/** Alert description */
17
description?: React.ReactNode;
18
/** Alert color theme */
19
color?: "default" | "primary" | "secondary" | "success" | "warning" | "danger";
20
/** Alert variant */
21
variant?: "solid" | "bordered" | "light" | "flat" | "faded";
22
/** Border radius */
23
radius?: "none" | "sm" | "md" | "lg" | "full";
24
/** Start content (icon, etc.) */
25
startContent?: React.ReactNode;
26
/** End content (action buttons, etc.) */
27
endContent?: React.ReactNode;
28
/** Whether alert is closeable */
29
isClosable?: boolean;
30
/** Whether alert is visible */
31
isVisible?: boolean;
32
/** Default visibility state */
33
defaultVisible?: boolean;
34
/** Hide icon */
35
hideIconWrapper?: boolean;
36
/** Custom CSS class */
37
className?: string;
38
/** Slot-based styling */
39
classNames?: SlotsToClasses<AlertSlots>;
40
/** Close event handler */
41
onClose?: () => void;
42
/** Visibility change handler */
43
onVisibilityChange?: (isVisible: boolean) => void;
44
}
45
46
type AlertSlots =
47
| "base" | "wrapper" | "iconWrapper" | "icon"
48
| "mainWrapper" | "title" | "description"
49
| "closeButton" | "closeIcon";
50
51
function Alert(props: AlertProps): JSX.Element;
52
53
/**
54
* Hook for Alert state management
55
*/
56
function useAlert(props: AlertProps): {
57
Component: React.ElementType;
58
slots: Record<AlertSlots, string>;
59
classNames: SlotsToClasses<AlertSlots>;
60
isVisible: boolean;
61
getAlertProps: () => any;
62
getWrapperProps: () => any;
63
getIconWrapperProps: () => any;
64
getMainWrapperProps: () => any;
65
getTitleProps: () => any;
66
getDescriptionProps: () => any;
67
getCloseButtonProps: () => any;
68
};
69
```
70
71
**Alert Usage Examples:**
72
73
```typescript
74
import { Alert, Button } from "@nextui-org/react";
75
import { InfoIcon, CheckIcon, WarningIcon } from "@heroicons/react/24/solid";
76
77
function AlertExamples() {
78
const [isVisible, setIsVisible] = useState(true);
79
80
return (
81
<div className="space-y-4">
82
{/* Basic alerts */}
83
<Alert color="primary" title="Info Alert">
84
This is an informational message.
85
</Alert>
86
87
<Alert
88
color="success"
89
variant="bordered"
90
startContent={<CheckIcon className="w-5 h-5" />}
91
title="Success!"
92
description="Your action was completed successfully."
93
/>
94
95
<Alert
96
color="warning"
97
variant="flat"
98
startContent={<WarningIcon className="w-5 h-5" />}
99
title="Warning"
100
description="Please review the following items before continuing."
101
/>
102
103
<Alert
104
color="danger"
105
variant="light"
106
title="Error"
107
description="Something went wrong. Please try again."
108
endContent={
109
<Button color="danger" size="sm" variant="flat">
110
Retry
111
</Button>
112
}
113
/>
114
115
{/* Closeable alert */}
116
<Alert
117
color="secondary"
118
title="Dismissible Alert"
119
description="This alert can be closed by clicking the X button."
120
isClosable
121
isVisible={isVisible}
122
onVisibilityChange={setIsVisible}
123
/>
124
</div>
125
);
126
}
127
```
128
129
### Progress
130
131
Linear progress bar component for displaying completion status with customizable appearance and value formatting.
132
133
```typescript { .api }
134
interface ProgressProps {
135
/** Progress label */
136
label?: React.ReactNode;
137
/** Progress size */
138
size?: "sm" | "md" | "lg";
139
/** Border radius */
140
radius?: "none" | "sm" | "md" | "lg" | "full";
141
/** Color theme */
142
color?: "default" | "primary" | "secondary" | "success" | "warning" | "danger";
143
/** Current progress value (0-100) */
144
value?: number;
145
/** Minimum value */
146
minValue?: number;
147
/** Maximum value */
148
maxValue?: number;
149
/** Indeterminate loading state */
150
isIndeterminate?: boolean;
151
/** Show value label */
152
showValueLabel?: boolean;
153
/** Custom value label */
154
valueLabel?: React.ReactNode;
155
/** Number formatting options */
156
formatOptions?: Intl.NumberFormatOptions;
157
/** Disabled state */
158
isDisabled?: boolean;
159
/** Disable animations */
160
disableAnimation?: boolean;
161
/** Custom CSS class */
162
className?: string;
163
/** Slot-based styling */
164
classNames?: SlotsToClasses<ProgressSlots>;
165
/** Value change handler */
166
onValueChange?: (value: number) => void;
167
}
168
169
type ProgressSlots = "base" | "labelWrapper" | "label" | "value" | "track" | "indicator";
170
171
function Progress(props: ProgressProps): JSX.Element;
172
173
/**
174
* Hook for Progress state management
175
*/
176
function useProgress(props: ProgressProps): {
177
Component: React.ElementType;
178
slots: Record<ProgressSlots, string>;
179
classNames: SlotsToClasses<ProgressSlots>;
180
progressBarProps: any;
181
labelProps: any;
182
percentage: number;
183
getProgressBarProps: () => any;
184
getLabelProps: () => any;
185
};
186
```
187
188
### Circular Progress
189
190
Circular progress indicator for compact loading states and completion visualization.
191
192
```typescript { .api }
193
interface CircularProgressProps {
194
/** Progress label */
195
label?: React.ReactNode;
196
/** Progress size */
197
size?: "sm" | "md" | "lg";
198
/** Color theme */
199
color?: "default" | "primary" | "secondary" | "success" | "warning" | "danger";
200
/** Current progress value (0-100) */
201
value?: number;
202
/** Minimum value */
203
minValue?: number;
204
/** Maximum value */
205
maxValue?: number;
206
/** Indeterminate loading state */
207
isIndeterminate?: boolean;
208
/** Show value label */
209
showValueLabel?: boolean;
210
/** Custom value label */
211
valueLabel?: React.ReactNode;
212
/** Number formatting options */
213
formatOptions?: Intl.NumberFormatOptions;
214
/** Disabled state */
215
isDisabled?: boolean;
216
/** Disable animations */
217
disableAnimation?: boolean;
218
/** Stroke width */
219
strokeWidth?: number;
220
/** Custom CSS class */
221
className?: string;
222
/** Slot-based styling */
223
classNames?: SlotsToClasses<CircularProgressSlots>;
224
/** Value change handler */
225
onValueChange?: (value: number) => void;
226
}
227
228
type CircularProgressSlots =
229
| "base" | "svgWrapper" | "svg" | "track" | "indicator"
230
| "value" | "label";
231
232
function CircularProgress(props: CircularProgressProps): JSX.Element;
233
```
234
235
**Progress Usage Examples:**
236
237
```typescript
238
import { Progress, CircularProgress, Button } from "@nextui-org/react";
239
240
function ProgressExamples() {
241
const [value, setValue] = useState(0);
242
243
useEffect(() => {
244
const interval = setInterval(() => {
245
setValue((v) => (v >= 100 ? 0 : v + 10));
246
}, 500);
247
248
return () => clearInterval(interval);
249
}, []);
250
251
return (
252
<div className="space-y-6">
253
{/* Linear progress */}
254
<div className="space-y-3">
255
<Progress
256
aria-label="Loading..."
257
size="md"
258
value={value}
259
color="success"
260
showValueLabel={true}
261
className="max-w-md"
262
/>
263
264
<Progress
265
label="Loading files..."
266
size="sm"
267
isIndeterminate
268
color="primary"
269
className="max-w-md"
270
/>
271
272
<Progress
273
size="lg"
274
radius="sm"
275
classNames={{
276
base: "max-w-md",
277
track: "drop-shadow-md border border-default",
278
indicator: "bg-gradient-to-r from-pink-500 to-yellow-500",
279
label: "tracking-wider font-medium text-default-600",
280
value: "text-foreground/60",
281
}}
282
label="Custom styled progress"
283
value={65}
284
showValueLabel={true}
285
/>
286
</div>
287
288
{/* Circular progress */}
289
<div className="flex gap-6 items-center">
290
<CircularProgress
291
aria-label="Loading..."
292
size="lg"
293
value={value}
294
color="warning"
295
showValueLabel={true}
296
/>
297
298
<CircularProgress
299
label="CPU Usage"
300
size="lg"
301
value={70}
302
color="danger"
303
formatOptions={{ style: "unit", unit: "percent" }}
304
showValueLabel={true}
305
/>
306
307
<CircularProgress
308
size="sm"
309
isIndeterminate
310
color="secondary"
311
aria-label="Loading..."
312
/>
313
</div>
314
</div>
315
);
316
}
317
```
318
319
### Spinner
320
321
Loading spinner component for indicating processing states with various sizes and colors.
322
323
```typescript { .api }
324
interface SpinnerProps {
325
/** Spinner label */
326
label?: React.ReactNode;
327
/** Spinner size */
328
size?: "sm" | "md" | "lg";
329
/** Spinner color */
330
color?: "current" | "white" | "default" | "primary" | "secondary" | "success" | "warning" | "danger";
331
/** Label color */
332
labelColor?: "foreground" | "primary" | "secondary" | "success" | "warning" | "danger";
333
/** Custom CSS class */
334
className?: string;
335
/** Slot-based styling */
336
classNames?: SlotsToClasses<SpinnerSlots>;
337
}
338
339
type SpinnerSlots = "base" | "wrapper" | "circle1" | "circle2" | "label";
340
341
function Spinner(props: SpinnerProps): JSX.Element;
342
343
/**
344
* Hook for Spinner state management
345
*/
346
function useSpinner(props: SpinnerProps): {
347
Component: React.ElementType;
348
slots: Record<SpinnerSlots, string>;
349
classNames: SlotsToClasses<SpinnerSlots>;
350
getSpinnerProps: () => any;
351
getWrapperProps: () => any;
352
getLabelProps: () => any;
353
};
354
```
355
356
**Spinner Usage Examples:**
357
358
```typescript
359
import { Spinner, Card, CardBody } from "@nextui-org/react";
360
361
function SpinnerExamples() {
362
return (
363
<div className="space-y-6">
364
{/* Basic spinners */}
365
<div className="flex gap-4 items-center">
366
<Spinner size="sm" />
367
<Spinner size="md" />
368
<Spinner size="lg" />
369
</div>
370
371
{/* Colored spinners */}
372
<div className="flex gap-4 items-center">
373
<Spinner color="primary" />
374
<Spinner color="secondary" />
375
<Spinner color="success" />
376
<Spinner color="warning" />
377
<Spinner color="danger" />
378
</div>
379
380
{/* Spinner with labels */}
381
<div className="flex flex-col gap-4">
382
<Spinner label="Loading..." />
383
384
<Spinner
385
label="Processing your request"
386
color="warning"
387
labelColor="warning"
388
/>
389
</div>
390
391
{/* Spinner in content */}
392
<Card>
393
<CardBody className="flex items-center justify-center py-8">
394
<Spinner
395
size="lg"
396
label="Loading content..."
397
color="primary"
398
/>
399
</CardBody>
400
</Card>
401
</div>
402
);
403
}
404
```
405
406
### Skeleton
407
408
Placeholder loading component that mimics content structure during data loading.
409
410
```typescript { .api }
411
interface SkeletonProps {
412
/** Skeleton content */
413
children?: React.ReactNode;
414
/** Whether skeleton is loading */
415
isLoaded?: boolean;
416
/** Disable animations */
417
disableAnimation?: boolean;
418
/** Custom CSS class */
419
className?: string;
420
}
421
422
function Skeleton(props: SkeletonProps): JSX.Element;
423
424
/**
425
* Hook for Skeleton state management
426
*/
427
function useSkeleton(props: SkeletonProps): {
428
Component: React.ElementType;
429
getSkeletonProps: () => any;
430
};
431
```
432
433
**Skeleton Usage Examples:**
434
435
```typescript
436
import { Skeleton, Card, CardHeader, CardBody, Avatar } from "@nextui-org/react";
437
438
function SkeletonExamples() {
439
const [isLoaded, setIsLoaded] = useState(false);
440
441
useEffect(() => {
442
const timer = setTimeout(() => setIsLoaded(true), 3000);
443
return () => clearTimeout(timer);
444
}, []);
445
446
return (
447
<div className="space-y-6">
448
{/* Content skeleton */}
449
<Card className="w-[200px] space-y-5 p-4" radius="lg">
450
<Skeleton isLoaded={isLoaded} className="rounded-lg">
451
<div className="h-24 rounded-lg bg-secondary"></div>
452
</Skeleton>
453
<div className="space-y-3">
454
<Skeleton isLoaded={isLoaded} className="w-3/5 rounded-lg">
455
<div className="h-3 w-full rounded-lg bg-secondary"></div>
456
</Skeleton>
457
<Skeleton isLoaded={isLoaded} className="w-4/5 rounded-lg">
458
<div className="h-3 w-full rounded-lg bg-secondary-300"></div>
459
</Skeleton>
460
<Skeleton isLoaded={isLoaded} className="w-2/5 rounded-lg">
461
<div className="h-3 w-full rounded-lg bg-secondary-200"></div>
462
</Skeleton>
463
</div>
464
</Card>
465
466
{/* User profile skeleton */}
467
<Card className="max-w-[300px]">
468
<CardHeader className="justify-between">
469
<div className="flex gap-5">
470
<Skeleton isLoaded={isLoaded} className="flex rounded-full w-12 h-12">
471
<Avatar
472
isBordered
473
radius="full"
474
size="md"
475
src="https://nextui.org/avatars/avatar-1.png"
476
/>
477
</Skeleton>
478
<div className="flex flex-col gap-1 items-start justify-center">
479
<Skeleton isLoaded={isLoaded} className="w-20 rounded-lg">
480
<h4 className="text-small font-semibold leading-none text-default-600">
481
Zoey Lang
482
</h4>
483
</Skeleton>
484
<Skeleton isLoaded={isLoaded} className="w-16 rounded-lg">
485
<h5 className="text-small tracking-tight text-default-400">
486
@zoeylang
487
</h5>
488
</Skeleton>
489
</div>
490
</div>
491
<Skeleton isLoaded={isLoaded} className="rounded-lg">
492
<Button color="primary" radius="full" size="sm">
493
Follow
494
</Button>
495
</Skeleton>
496
</CardHeader>
497
<CardBody className="px-3 py-0 text-small text-default-400">
498
<Skeleton isLoaded={isLoaded} className="rounded-lg">
499
<p>
500
Frontend developer and UI/UX enthusiast. Join me on this coding adventure!
501
</p>
502
</Skeleton>
503
</CardBody>
504
</Card>
505
</div>
506
);
507
}
508
```
509
510
### Tooltip
511
512
Contextual information overlay that appears on hover or focus to provide additional details.
513
514
```typescript { .api }
515
interface TooltipProps {
516
/** Target element to attach tooltip to */
517
children: React.ReactElement;
518
/** Tooltip content */
519
content?: React.ReactNode;
520
/** Whether tooltip is open */
521
isOpen?: boolean;
522
/** Default open state */
523
defaultOpen?: boolean;
524
/** Tooltip placement */
525
placement?: Placement;
526
/** Show delay in milliseconds */
527
delay?: number;
528
/** Close delay in milliseconds */
529
closeDelay?: number;
530
/** Disabled state */
531
isDisabled?: boolean;
532
/** Whether tooltip should flip to fit */
533
shouldFlip?: boolean;
534
/** Container padding for flip calculations */
535
containerPadding?: number;
536
/** Offset from target */
537
offset?: number;
538
/** Cross-axis offset */
539
crossOffset?: number;
540
/** Show arrow pointer */
541
showArrow?: boolean;
542
/** Border radius */
543
radius?: "none" | "sm" | "md" | "lg" | "full";
544
/** Tooltip size */
545
size?: "sm" | "md" | "lg";
546
/** Color theme */
547
color?: "default" | "foreground" | "primary" | "secondary" | "success" | "warning" | "danger";
548
/** Motion configuration */
549
motionProps?: MotionProps;
550
/** Custom CSS class */
551
className?: string;
552
/** Slot-based styling */
553
classNames?: SlotsToClasses<TooltipSlots>;
554
/** Open change handler */
555
onOpenChange?: (isOpen: boolean) => void;
556
}
557
558
type TooltipSlots = "base" | "arrow" | "content";
559
560
type Placement =
561
| "top" | "top-start" | "top-end"
562
| "bottom" | "bottom-start" | "bottom-end"
563
| "right" | "right-start" | "right-end"
564
| "left" | "left-start" | "left-end";
565
566
function Tooltip(props: TooltipProps): JSX.Element;
567
568
/**
569
* Hook for Tooltip state management
570
*/
571
function useTooltip(props: TooltipProps): {
572
Component: React.ElementType;
573
isOpen: boolean;
574
disableAnimation: boolean;
575
getTooltipProps: () => any;
576
getTriggerProps: () => any;
577
getTooltipContentProps: () => any;
578
};
579
```
580
581
**Tooltip Usage Examples:**
582
583
```typescript
584
import { Tooltip, Button } from "@nextui-org/react";
585
import { DeleteIcon, EditIcon, EyeIcon } from "@heroicons/react/24/solid";
586
587
function TooltipExamples() {
588
return (
589
<div className="space-y-6">
590
{/* Basic tooltips */}
591
<div className="flex gap-4 items-center">
592
<Tooltip content="I am a tooltip">
593
<Button>Hover me</Button>
594
</Tooltip>
595
596
<Tooltip content="Tooltip with arrow" showArrow>
597
<Button color="primary">With Arrow</Button>
598
</Tooltip>
599
</div>
600
601
{/* Colored tooltips */}
602
<div className="flex gap-4 items-center">
603
<Tooltip content="Edit" color="success" showArrow>
604
<Button isIconOnly color="success" variant="light">
605
<EditIcon className="w-4 h-4" />
606
</Button>
607
</Tooltip>
608
609
<Tooltip content="View" color="secondary" showArrow>
610
<Button isIconOnly color="secondary" variant="light">
611
<EyeIcon className="w-4 h-4" />
612
</Button>
613
</Tooltip>
614
615
<Tooltip content="Delete" color="danger" showArrow>
616
<Button isIconOnly color="danger" variant="light">
617
<DeleteIcon className="w-4 h-4" />
618
</Button>
619
</Tooltip>
620
</div>
621
622
{/* Placement variations */}
623
<div className="flex flex-wrap gap-4">
624
{["top", "bottom", "left", "right"].map((placement) => (
625
<Tooltip
626
key={placement}
627
content={`Tooltip on ${placement}`}
628
placement={placement as Placement}
629
showArrow
630
>
631
<Button variant="bordered" className="capitalize">
632
{placement}
633
</Button>
634
</Tooltip>
635
))}
636
</div>
637
638
{/* Custom content tooltip */}
639
<Tooltip
640
showArrow
641
content={
642
<div className="px-1 py-2">
643
<div className="text-small font-bold">Custom Content</div>
644
<div className="text-tiny">This tooltip has custom styling</div>
645
</div>
646
}
647
>
648
<Button color="secondary" variant="flat">
649
Custom Content
650
</Button>
651
</Tooltip>
652
</div>
653
);
654
}
655
```
656
657
## Feedback Component Types
658
659
```typescript { .api }
660
// Common feedback types
661
type FeedbackSize = "sm" | "md" | "lg";
662
type FeedbackColor = "default" | "primary" | "secondary" | "success" | "warning" | "danger";
663
type FeedbackRadius = "none" | "sm" | "md" | "lg" | "full";
664
665
// Alert types
666
interface AlertState {
667
isVisible: boolean;
668
isClosable: boolean;
669
color: FeedbackColor;
670
variant: "solid" | "bordered" | "light" | "flat" | "faded";
671
}
672
673
// Progress types
674
interface ProgressState {
675
value: number;
676
minValue: number;
677
maxValue: number;
678
percentage: number;
679
isIndeterminate: boolean;
680
valueLabel: string;
681
}
682
683
// Tooltip types
684
interface TooltipState {
685
isOpen: boolean;
686
placement: Placement;
687
isDisabled: boolean;
688
showArrow: boolean;
689
delay: number;
690
closeDelay: number;
691
}
692
693
// Motion configuration for animations
694
interface MotionProps {
695
initial?: any;
696
animate?: any;
697
exit?: any;
698
transition?: any;
699
variants?: any;
700
whileHover?: any;
701
whileTap?: any;
702
whileFocus?: any;
703
whileInView?: any;
704
}
705
706
// Placement type for positioning
707
type PlacementAxis = "top" | "bottom" | "left" | "right";
708
type PlacementAlign = "start" | "end";
709
type Placement = PlacementAxis | `${PlacementAxis}-${PlacementAlign}`;
710
711
// Number formatting for progress values
712
interface ProgressFormatOptions extends Intl.NumberFormatOptions {
713
style?: "decimal" | "currency" | "percent" | "unit";
714
unit?: string;
715
currency?: string;
716
minimumFractionDigits?: number;
717
maximumFractionDigits?: number;
718
}
719
```
720
721
## Integration Examples
722
723
### Loading States Pattern
724
725
```typescript
726
import {
727
Skeleton, Spinner, Progress, Alert, Button, Card, CardBody
728
} from "@nextui-org/react";
729
730
function LoadingStatesExample() {
731
const [loadingState, setLoadingState] = useState<'idle' | 'loading' | 'error' | 'success'>('idle');
732
const [progress, setProgress] = useState(0);
733
734
const handleAction = async () => {
735
setLoadingState('loading');
736
setProgress(0);
737
738
try {
739
// Simulate progress updates
740
for (let i = 0; i <= 100; i += 10) {
741
setProgress(i);
742
await new Promise(resolve => setTimeout(resolve, 200));
743
}
744
745
setLoadingState('success');
746
} catch (error) {
747
setLoadingState('error');
748
}
749
};
750
751
return (
752
<div className="space-y-6 max-w-md">
753
{loadingState === 'loading' && (
754
<Card>
755
<CardBody className="space-y-4">
756
<div className="flex items-center gap-4">
757
<Spinner size="sm" color="primary" />
758
<span>Processing...</span>
759
</div>
760
<Progress
761
value={progress}
762
color="primary"
763
showValueLabel
764
/>
765
</CardBody>
766
</Card>
767
)}
768
769
{loadingState === 'success' && (
770
<Alert
771
color="success"
772
title="Success!"
773
description="Your action was completed successfully."
774
/>
775
)}
776
777
{loadingState === 'error' && (
778
<Alert
779
color="danger"
780
title="Error"
781
description="Something went wrong. Please try again."
782
endContent={
783
<Button
784
color="danger"
785
variant="flat"
786
size="sm"
787
onPress={() => setLoadingState('idle')}
788
>
789
Retry
790
</Button>
791
}
792
/>
793
)}
794
795
<Button
796
color="primary"
797
onPress={handleAction}
798
isDisabled={loadingState === 'loading'}
799
>
800
{loadingState === 'loading' ? 'Processing...' : 'Start Action'}
801
</Button>
802
</div>
803
);
804
}
805
```
806
807
### Notification System
808
809
```typescript
810
import { Alert, Button } from "@nextui-org/react";
811
812
function NotificationSystem() {
813
const [notifications, setNotifications] = useState<Array<{
814
id: string;
815
type: 'success' | 'warning' | 'danger' | 'info';
816
title: string;
817
message: string;
818
}>>([]);
819
820
const addNotification = (type: string, title: string, message: string) => {
821
const notification = {
822
id: Date.now().toString(),
823
type,
824
title,
825
message,
826
};
827
setNotifications(prev => [...prev, notification]);
828
829
// Auto remove after 5 seconds
830
setTimeout(() => {
831
removeNotification(notification.id);
832
}, 5000);
833
};
834
835
const removeNotification = (id: string) => {
836
setNotifications(prev => prev.filter(n => n.id !== id));
837
};
838
839
return (
840
<div className="space-y-4">
841
{/* Notification triggers */}
842
<div className="flex gap-2">
843
<Button
844
color="success"
845
onPress={() => addNotification('success', 'Success', 'Operation completed successfully!')}
846
>
847
Success
848
</Button>
849
<Button
850
color="warning"
851
onPress={() => addNotification('warning', 'Warning', 'Please review your input.')}
852
>
853
Warning
854
</Button>
855
<Button
856
color="danger"
857
onPress={() => addNotification('danger', 'Error', 'Something went wrong.')}
858
>
859
Error
860
</Button>
861
</div>
862
863
{/* Notifications display */}
864
<div className="fixed top-4 right-4 space-y-2 z-50">
865
{notifications.map((notification) => (
866
<Alert
867
key={notification.id}
868
color={notification.type}
869
title={notification.title}
870
description={notification.message}
871
isClosable
872
onClose={() => removeNotification(notification.id)}
873
className="max-w-sm"
874
/>
875
))}
876
</div>
877
</div>
878
);
879
}
880
```