0
# Overlay Components
1
2
@mantine/core provides comprehensive overlay components for creating modal dialogs, popovers, tooltips, and other floating elements. These components handle complex positioning, focus management, and accessibility requirements.
3
4
## Modal
5
6
Full-featured modal dialog component with backdrop, transitions, and accessibility.
7
8
```typescript { .api }
9
interface ModalProps {
10
/** If true, modal is opened */
11
opened: boolean;
12
/** Called when modal should be closed */
13
onClose: () => void;
14
/** Modal title */
15
title?: React.ReactNode;
16
/** Modal content */
17
children?: React.ReactNode;
18
/** Modal size */
19
size?: MantineSize | string | number;
20
/** If true, modal will be centered */
21
centered?: boolean;
22
/** If true, modal cannot be closed by clicking outside or pressing Escape */
23
closeOnClickOutside?: boolean;
24
/** If true, modal cannot be closed by pressing Escape */
25
closeOnEscape?: boolean;
26
/** If true, close button will not be rendered */
27
withCloseButton?: boolean;
28
/** Modal z-index */
29
zIndex?: number;
30
/** Overlay props */
31
overlayProps?: OverlayProps;
32
/** Transition props */
33
transitionProps?: TransitionProps;
34
/** Modal radius */
35
radius?: MantineRadius;
36
/** If true, modal will have full screen on mobile */
37
fullScreen?: boolean;
38
/** Modal keep mounted */
39
keepMounted?: boolean;
40
}
41
42
interface ModalRootProps {
43
/** Root children */
44
children: React.ReactNode;
45
/** If true, modal is opened */
46
opened: boolean;
47
/** Called when modal should be closed */
48
onClose: () => void;
49
/** Modal z-index */
50
zIndex?: number;
51
/** If true, modal cannot be closed by clicking outside */
52
closeOnClickOutside?: boolean;
53
/** If true, modal cannot be closed by pressing Escape */
54
closeOnEscape?: boolean;
55
/** If true, modal will be centered */
56
centered?: boolean;
57
/** Transition props */
58
transitionProps?: TransitionProps;
59
/** If true, modal will have full screen on mobile */
60
fullScreen?: boolean;
61
}
62
63
interface ModalOverlayProps {
64
/** Overlay background color */
65
color?: string;
66
/** Overlay background opacity */
67
backgroundOpacity?: number;
68
/** Overlay blur */
69
blur?: number;
70
}
71
72
interface ModalContentProps {
73
/** Content children */
74
children: React.ReactNode;
75
/** Modal size */
76
size?: MantineSize | string | number;
77
/** Modal radius */
78
radius?: MantineRadius;
79
}
80
81
interface ModalHeaderProps {
82
/** Header children */
83
children: React.ReactNode;
84
}
85
86
interface ModalTitleProps {
87
/** Title content */
88
children: React.ReactNode;
89
}
90
91
interface ModalCloseButtonProps {
92
/** Close button aria-label */
93
'aria-label'?: string;
94
}
95
96
interface ModalBodyProps {
97
/** Body children */
98
children: React.ReactNode;
99
}
100
```
101
102
**Usage Example:**
103
104
```tsx
105
import { Modal, Button, TextInput, Group } from '@mantine/core';
106
import { useDisclosure } from '@mantine/hooks';
107
108
function ModalDemo() {
109
const [opened, { open, close }] = useDisclosure(false);
110
111
return (
112
<>
113
<Modal opened={opened} onClose={close} title="Authentication">
114
<TextInput label="Email" placeholder="your@email.com" />
115
<TextInput label="Password" type="password" mt="md" />
116
<Group mt="md" justify="flex-end">
117
<Button onClick={close}>Login</Button>
118
</Group>
119
</Modal>
120
121
<Button onClick={open}>Open modal</Button>
122
</>
123
);
124
}
125
126
// Compound components usage
127
function CompoundModal() {
128
const [opened, { open, close }] = useDisclosure(false);
129
130
return (
131
<>
132
<Modal.Root opened={opened} onClose={close}>
133
<Modal.Overlay />
134
<Modal.Content>
135
<Modal.Header>
136
<Modal.Title>Modal title</Modal.Title>
137
<Modal.CloseButton />
138
</Modal.Header>
139
<Modal.Body>
140
Modal content
141
</Modal.Body>
142
</Modal.Content>
143
</Modal.Root>
144
145
<Button onClick={open}>Open modal</Button>
146
</>
147
);
148
}
149
```
150
151
## ModalBase
152
153
Unstyled base modal implementation for custom modal components.
154
155
```typescript { .api }
156
interface ModalBaseProps {
157
/** If true, modal is opened */
158
opened: boolean;
159
/** Called when modal should be closed */
160
onClose: () => void;
161
/** Modal content */
162
children?: React.ReactNode;
163
/** Modal size */
164
size?: MantineSize | string | number;
165
/** If true, modal will be centered */
166
centered?: boolean;
167
/** If true, modal cannot be closed by clicking outside */
168
closeOnClickOutside?: boolean;
169
/** If true, modal cannot be closed by pressing Escape */
170
closeOnEscape?: boolean;
171
/** Modal z-index */
172
zIndex?: number;
173
/** Overlay props */
174
overlayProps?: OverlayProps;
175
/** Transition props */
176
transitionProps?: TransitionProps;
177
/** If true, modal will have full screen on mobile */
178
fullScreen?: boolean;
179
}
180
```
181
182
## Drawer
183
184
Slide-out drawer component for navigation or content panels.
185
186
```typescript { .api }
187
interface DrawerProps {
188
/** If true, drawer is opened */
189
opened: boolean;
190
/** Called when drawer should be closed */
191
onClose: () => void;
192
/** Drawer title */
193
title?: React.ReactNode;
194
/** Drawer content */
195
children?: React.ReactNode;
196
/** Drawer position */
197
position?: 'top' | 'bottom' | 'left' | 'right';
198
/** Drawer size */
199
size?: MantineSize | string | number;
200
/** If true, drawer cannot be closed by clicking outside */
201
closeOnClickOutside?: boolean;
202
/** If true, drawer cannot be closed by pressing Escape */
203
closeOnEscape?: boolean;
204
/** If true, close button will not be rendered */
205
withCloseButton?: boolean;
206
/** Drawer z-index */
207
zIndex?: number;
208
/** Overlay props */
209
overlayProps?: OverlayProps;
210
/** Transition props */
211
transitionProps?: TransitionProps;
212
/** If true, drawer will have scroll area */
213
scrollAreaComponent?: any;
214
}
215
216
interface DrawerRootProps extends Pick<DrawerProps, 'opened' | 'onClose' | 'position' | 'size' | 'closeOnClickOutside' | 'closeOnEscape' | 'zIndex' | 'transitionProps'> {
217
/** Root children */
218
children: React.ReactNode;
219
}
220
221
interface DrawerOverlayProps extends ModalOverlayProps {}
222
223
interface DrawerContentProps {
224
/** Content children */
225
children: React.ReactNode;
226
}
227
228
interface DrawerHeaderProps {
229
/** Header children */
230
children: React.ReactNode;
231
}
232
233
interface DrawerTitleProps {
234
/** Title content */
235
children: React.ReactNode;
236
}
237
238
interface DrawerCloseButtonProps {
239
/** Close button aria-label */
240
'aria-label'?: string;
241
}
242
243
interface DrawerBodyProps {
244
/** Body children */
245
children: React.ReactNode;
246
}
247
```
248
249
**Usage Example:**
250
251
```tsx
252
import { Drawer, Button, Group } from '@mantine/core';
253
import { useDisclosure } from '@mantine/hooks';
254
255
function DrawerDemo() {
256
const [opened, { open, close }] = useDisclosure(false);
257
258
return (
259
<>
260
<Drawer opened={opened} onClose={close} title="Authentication">
261
<p>Drawer content...</p>
262
<Group mt="xl">
263
<Button variant="outline" onClick={close}>
264
Cancel
265
</Button>
266
<Button onClick={close}>
267
Save
268
</Button>
269
</Group>
270
</Drawer>
271
272
<Button onClick={open}>Open Drawer</Button>
273
</>
274
);
275
}
276
277
// Different positions
278
function DrawerPositions() {
279
return (
280
<>
281
<Drawer position="left" opened={opened} onClose={close}>
282
Left drawer
283
</Drawer>
284
<Drawer position="right" opened={opened} onClose={close}>
285
Right drawer
286
</Drawer>
287
<Drawer position="top" opened={opened} onClose={close}>
288
Top drawer
289
</Drawer>
290
<Drawer position="bottom" opened={opened} onClose={close}>
291
Bottom drawer
292
</Drawer>
293
</>
294
);
295
}
296
```
297
298
## Popover
299
300
Floating popover component with positioning and trigger options.
301
302
```typescript { .api }
303
interface PopoverProps {
304
/** Popover content */
305
children: React.ReactNode;
306
/** If true, popover is opened */
307
opened?: boolean;
308
/** Default opened state */
309
defaultOpened?: boolean;
310
/** Called when popover state changes */
311
onChange?: (opened: boolean) => void;
312
/** Popover position relative to target */
313
position?: FloatingPosition;
314
/** Popover placement offset */
315
offset?: number;
316
/** If true, popover will be closed on outside click */
317
closeOnClickOutside?: boolean;
318
/** If true, popover will be closed on escape key press */
319
closeOnEscape?: boolean;
320
/** If true, popover will be closed when target element is clicked */
321
clickOutsideEvents?: string[];
322
/** Popover width */
323
width?: number | 'target';
324
/** If true, popover will have arrow */
325
withArrow?: boolean;
326
/** Arrow size */
327
arrowSize?: number;
328
/** Arrow position */
329
arrowPosition?: 'center' | 'side';
330
/** Arrow offset */
331
arrowOffset?: number;
332
/** If true, popover will be disabled */
333
disabled?: boolean;
334
/** Popover z-index */
335
zIndex?: number;
336
/** Popover radius */
337
radius?: MantineRadius;
338
/** Popover shadow */
339
shadow?: MantineShadow;
340
/** If true, popover will have focus trap */
341
trapFocus?: boolean;
342
/** If true, popover will return focus to trigger when closed */
343
returnFocus?: boolean;
344
}
345
346
interface PopoverTargetProps {
347
/** Target element */
348
children: React.ReactElement;
349
/** Popover reference type */
350
refProp?: string;
351
}
352
353
interface PopoverDropdownProps {
354
/** Dropdown content */
355
children: React.ReactNode;
356
}
357
```
358
359
**Usage Example:**
360
361
```tsx
362
import { Popover, Button, Text } from '@mantine/core';
363
364
function PopoverDemo() {
365
return (
366
<Popover width={200} position="bottom" withArrow shadow="md">
367
<Popover.Target>
368
<Button>Toggle popover</Button>
369
</Popover.Target>
370
<Popover.Dropdown>
371
<Text size="sm">This is uncontrolled popover</Text>
372
</Popover.Dropdown>
373
</Popover>
374
);
375
}
376
377
// Controlled popover
378
function ControlledPopover() {
379
const [opened, setOpened] = useState(false);
380
381
return (
382
<Popover opened={opened} onChange={setOpened}>
383
<Popover.Target>
384
<Button onClick={() => setOpened((o) => !o)}>
385
Toggle popover
386
</Button>
387
</Popover.Target>
388
<Popover.Dropdown>
389
<Text size="sm">Controlled popover content</Text>
390
</Popover.Dropdown>
391
</Popover>
392
);
393
}
394
```
395
396
## HoverCard
397
398
Popover that opens on hover with delay support.
399
400
```typescript { .api }
401
interface HoverCardProps extends Omit<PopoverProps, 'opened' | 'onChange'> {
402
/** Open delay in ms */
403
openDelay?: number;
404
/** Close delay in ms */
405
closeDelay?: number;
406
/** Called when hover card opens */
407
onOpen?: () => void;
408
/** Called when hover card closes */
409
onClose?: () => void;
410
}
411
412
interface HoverCardTargetProps {
413
/** Target element */
414
children: React.ReactElement;
415
}
416
417
interface HoverCardDropdownProps {
418
/** Dropdown content */
419
children: React.ReactNode;
420
}
421
```
422
423
**Usage Example:**
424
425
```tsx
426
import { HoverCard, Button, Text, Avatar, Group } from '@mantine/core';
427
428
function HoverCardDemo() {
429
return (
430
<Group justify="center">
431
<HoverCard width={280} shadow="md">
432
<HoverCard.Target>
433
<Avatar
434
src="https://images.unsplash.com/photo-1623582854588-d60de57fa33f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80"
435
radius="xl"
436
/>
437
</HoverCard.Target>
438
<HoverCard.Dropdown>
439
<div>
440
<Avatar
441
src="https://images.unsplash.com/photo-1623582854588-d60de57fa33f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80"
442
size={94}
443
radius="md"
444
/>
445
<Text fz="sm" tt="uppercase" fw={700} c="dimmed">
446
Software engineer
447
</Text>
448
449
<Text fz="lg" fw={500} mt={-5}>
450
Jane Doe
451
</Text>
452
453
<Text fz="sm" c="dimmed">
454
Jane.Doe@email.com β’ +47 333 22 11
455
</Text>
456
</div>
457
</HoverCard.Dropdown>
458
</HoverCard>
459
</Group>
460
);
461
}
462
```
463
464
## Tooltip
465
466
Simple tooltip component for providing additional information on hover.
467
468
```typescript { .api }
469
interface TooltipProps {
470
/** Tooltip label */
471
label: React.ReactNode;
472
/** Tooltip children (trigger element) */
473
children: React.ReactNode;
474
/** Tooltip position */
475
position?: FloatingPosition;
476
/** If true, tooltip will be disabled */
477
disabled?: boolean;
478
/** Tooltip offset from target */
479
offset?: number;
480
/** Tooltip z-index */
481
zIndex?: number;
482
/** Tooltip radius */
483
radius?: MantineRadius;
484
/** Tooltip color */
485
color?: MantineColor;
486
/** If true, tooltip will have arrow */
487
withArrow?: boolean;
488
/** Arrow size */
489
arrowSize?: number;
490
/** Arrow position */
491
arrowPosition?: 'center' | 'side';
492
/** Arrow offset */
493
arrowOffset?: number;
494
/** Open delay in ms */
495
openDelay?: number;
496
/** Close delay in ms */
497
closeDelay?: number;
498
/** If true, tooltip will stay open when hovered */
499
keepMounted?: boolean;
500
/** Events that trigger tooltip */
501
events?: { hover: boolean; focus: boolean; touch: boolean };
502
}
503
504
interface TooltipFloatingProps extends TooltipProps {}
505
506
interface TooltipGroupProps {
507
/** Group children */
508
children: React.ReactNode;
509
/** Open delay for all tooltips in group */
510
openDelay?: number;
511
/** Close delay for all tooltips in group */
512
closeDelay?: number;
513
}
514
```
515
516
**Usage Example:**
517
518
```tsx
519
import { Tooltip, Button, Group } from '@mantine/core';
520
521
function TooltipDemo() {
522
return (
523
<Group>
524
<Tooltip label="This is tooltip">
525
<Button variant="outline">Button with tooltip</Button>
526
</Tooltip>
527
528
<Tooltip label="Colored tooltip" color="orange">
529
<Button variant="outline">Orange tooltip</Button>
530
</Tooltip>
531
532
<Tooltip
533
label="Tooltip with arrow"
534
withArrow
535
position="bottom"
536
>
537
<Button variant="outline">With arrow</Button>
538
</Tooltip>
539
</Group>
540
);
541
}
542
543
// Tooltip group
544
function TooltipGroupDemo() {
545
return (
546
<Tooltip.Group openDelay={500} closeDelay={100}>
547
<Group>
548
<Tooltip label="Tooltip 1">
549
<Button variant="outline">Button 1</Button>
550
</Tooltip>
551
<Tooltip label="Tooltip 2">
552
<Button variant="outline">Button 2</Button>
553
</Tooltip>
554
<Tooltip label="Tooltip 3">
555
<Button variant="outline">Button 3</Button>
556
</Tooltip>
557
</Group>
558
</Tooltip.Group>
559
);
560
}
561
```
562
563
## Menu
564
565
Context menu component with items, labels, and dividers.
566
567
```typescript { .api }
568
interface MenuProps {
569
/** Menu content */
570
children: React.ReactNode;
571
/** If true, menu is opened */
572
opened?: boolean;
573
/** Default opened state */
574
defaultOpened?: boolean;
575
/** Called when menu state changes */
576
onChange?: (opened: boolean) => void;
577
/** Menu position */
578
position?: FloatingPosition;
579
/** Menu offset */
580
offset?: number;
581
/** Menu trigger */
582
trigger?: 'click' | 'hover';
583
/** Open delay for hover trigger */
584
openDelay?: number;
585
/** Close delay for hover trigger */
586
closeDelay?: number;
587
/** If true, menu will be closed on item click */
588
closeOnItemClick?: boolean;
589
/** If true, menu will be closed on outside click */
590
closeOnClickOutside?: boolean;
591
/** If true, menu will be closed on escape */
592
closeOnEscape?: boolean;
593
/** Menu width */
594
width?: number | 'target';
595
/** If true, menu will have arrow */
596
withArrow?: boolean;
597
/** Arrow size */
598
arrowSize?: number;
599
/** Arrow position */
600
arrowPosition?: 'center' | 'side';
601
/** Arrow offset */
602
arrowOffset?: number;
603
/** Menu z-index */
604
zIndex?: number;
605
/** Menu radius */
606
radius?: MantineRadius;
607
/** Menu shadow */
608
shadow?: MantineShadow;
609
/** If true, menu will have focus trap */
610
trapFocus?: boolean;
611
/** If true, menu will return focus to trigger */
612
returnFocus?: boolean;
613
/** Keyboard events */
614
loop?: boolean;
615
}
616
617
interface MenuTargetProps {
618
/** Target element */
619
children: React.ReactElement;
620
}
621
622
interface MenuDropdownProps {
623
/** Dropdown content */
624
children: React.ReactNode;
625
}
626
627
interface MenuItemProps {
628
/** Item content */
629
children: React.ReactNode;
630
/** Item left section */
631
leftSection?: React.ReactNode;
632
/** Item right section */
633
rightSection?: React.ReactNode;
634
/** Item color when hovered/focused */
635
color?: MantineColor;
636
/** If true, item is disabled */
637
disabled?: boolean;
638
/** Called when item is clicked */
639
onClick?: () => void;
640
}
641
642
interface MenuLabelProps {
643
/** Label content */
644
children: React.ReactNode;
645
}
646
647
interface MenuDividerProps {
648
/** Divider label */
649
label?: React.ReactNode;
650
}
651
```
652
653
**Usage Example:**
654
655
```tsx
656
import { Menu, Button, Text, rem } from '@mantine/core';
657
658
function MenuDemo() {
659
return (
660
<Menu>
661
<Menu.Target>
662
<Button>Toggle menu</Button>
663
</Menu.Target>
664
665
<Menu.Dropdown>
666
<Menu.Label>Application</Menu.Label>
667
<Menu.Item leftSection="βοΈ">
668
Settings
669
</Menu.Item>
670
<Menu.Item leftSection="π¬">
671
Messages
672
</Menu.Item>
673
<Menu.Item leftSection="π·">
674
Gallery
675
</Menu.Item>
676
677
<Menu.Divider />
678
679
<Menu.Label>Danger zone</Menu.Label>
680
<Menu.Item leftSection="ποΈ">
681
Transfer my data
682
</Menu.Item>
683
<Menu.Item
684
color="red"
685
leftSection="ποΈ"
686
>
687
Delete my account
688
</Menu.Item>
689
</Menu.Dropdown>
690
</Menu>
691
);
692
}
693
```
694
695
## Dialog
696
697
Simple dialog component for notifications and confirmations.
698
699
```typescript { .api }
700
interface DialogProps {
701
/** If true, dialog is opened */
702
opened: boolean;
703
/** Called when dialog should be closed */
704
onClose?: () => void;
705
/** Dialog content */
706
children: React.ReactNode;
707
/** Dialog position */
708
position?: { top?: number; bottom?: number; left?: number; right?: number };
709
/** Dialog size */
710
size?: MantineSize | number;
711
/** Dialog radius */
712
radius?: MantineRadius;
713
/** Dialog shadow */
714
shadow?: MantineShadow;
715
/** Dialog z-index */
716
zIndex?: number;
717
/** If true, dialog will have close button */
718
withCloseButton?: boolean;
719
/** If true, dialog cannot be closed by clicking outside */
720
closeOnClickOutside?: boolean;
721
/** If true, dialog cannot be closed by pressing Escape */
722
closeOnEscape?: boolean;
723
/** Transition props */
724
transitionProps?: TransitionProps;
725
}
726
```
727
728
**Usage Example:**
729
730
```tsx
731
import { Dialog, Text, Button, Group } from '@mantine/core';
732
import { useDisclosure } from '@mantine/hooks';
733
734
function DialogDemo() {
735
const [opened, { toggle, close }] = useDisclosure(false);
736
737
return (
738
<>
739
<Group justify="center">
740
<Button onClick={toggle}>Toggle dialog</Button>
741
</Group>
742
743
<Dialog
744
opened={opened}
745
withCloseButton
746
onClose={close}
747
size="lg"
748
radius="md"
749
>
750
<Text size="sm" mb="xs" fw={500}>
751
Subscribe to email newsletter
752
</Text>
753
754
<Text size="xs">
755
Subscribe to our newsletter to stay up to date with the latest news
756
</Text>
757
</Dialog>
758
</>
759
);
760
}
761
```
762
763
## Overlay
764
765
Generic overlay component for creating backgrounds.
766
767
```typescript { .api }
768
interface OverlayProps {
769
/** Overlay background color */
770
color?: string;
771
/** Overlay opacity */
772
backgroundOpacity?: number;
773
/** Overlay blur amount */
774
blur?: number;
775
/** Overlay z-index */
776
zIndex?: number;
777
/** Overlay radius */
778
radius?: MantineRadius;
779
/** If true, overlay will cover entire viewport */
780
fixed?: boolean;
781
/** Overlay children */
782
children?: React.ReactNode;
783
/** Called when overlay is clicked */
784
onClick?: () => void;
785
}
786
```
787
788
**Usage Example:**
789
790
```tsx
791
import { Overlay, Button, Box } from '@mantine/core';
792
import { useState } from 'react';
793
794
function OverlayDemo() {
795
const [visible, setVisible] = useState(false);
796
797
return (
798
<Box pos="relative" h={200}>
799
{visible && <Overlay color="#000" backgroundOpacity={0.35} />}
800
<Button onClick={() => setVisible(!visible)}>
801
Toggle overlay
802
</Button>
803
</Box>
804
);
805
}
806
```
807
808
## Portal
809
810
Renders children in a different part of the DOM.
811
812
```typescript { .api }
813
interface PortalProps {
814
/** Portal children */
815
children: React.ReactNode;
816
/** Element where portal should be rendered */
817
target?: HTMLElement | string;
818
}
819
```
820
821
**Usage Example:**
822
823
```tsx
824
import { Portal, Box, Button } from '@mantine/core';
825
import { useState } from 'react';
826
827
function PortalDemo() {
828
const [opened, setOpened] = useState(false);
829
830
return (
831
<Box>
832
<Button onClick={() => setOpened(!opened)}>
833
Toggle portal
834
</Button>
835
836
{opened && (
837
<Portal>
838
<Box
839
style={{
840
position: 'absolute',
841
top: 0,
842
right: 0,
843
width: 200,
844
height: 100,
845
backgroundColor: 'red',
846
zIndex: 1000,
847
}}
848
>
849
Portal content rendered at document.body
850
</Box>
851
</Portal>
852
)}
853
</Box>
854
);
855
}
856
```
857
858
## Affix
859
860
Fixed positioned element that sticks to viewport edge.
861
862
```typescript { .api }
863
interface AffixProps {
864
/** Affix position */
865
position?: { top?: number; bottom?: number; left?: number; right?: number };
866
/** Affix z-index */
867
zIndex?: number;
868
/** If true, affix will be within scrollable container */
869
withinPortal?: boolean;
870
/** Affix children */
871
children: React.ReactNode;
872
/** Target element for within portal */
873
portalProps?: PortalProps;
874
}
875
```
876
877
**Usage Example:**
878
879
```tsx
880
import { Affix, Button, Text, Transition } from '@mantine/core';
881
import { useWindowScroll } from '@mantine/hooks';
882
883
function AffixDemo() {
884
const [scroll, scrollTo] = useWindowScroll();
885
886
return (
887
<>
888
<Text ta="center">Scroll down to see button</Text>
889
890
<Affix position={{ bottom: 20, right: 20 }}>
891
<Transition transition="slide-up" mounted={scroll.y > 0}>
892
{(transitionStyles) => (
893
<Button
894
leftSection="β"
895
style={transitionStyles}
896
onClick={() => scrollTo({ y: 0 })}
897
>
898
Scroll to top
899
</Button>
900
)}
901
</Transition>
902
</Affix>
903
</>
904
);
905
}
906
```
907
908
## FloatingIndicator
909
910
Animated indicator that moves between elements.
911
912
```typescript { .api }
913
interface FloatingIndicatorProps {
914
/** Target element selector or ref */
915
target: string | HTMLElement;
916
/** Parent element selector or ref */
917
parent?: string | HTMLElement;
918
/** Indicator className */
919
className?: string;
920
/** If true, indicator will be disabled */
921
disabled?: boolean;
922
}
923
```
924
925
**Usage Example:**
926
927
```tsx
928
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
929
import { useState } from 'react';
930
931
function FloatingIndicatorDemo() {
932
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
933
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
934
const [active, setActive] = useState(0);
935
936
const setControlRef = (index: number) => (node: HTMLButtonElement) => {
937
controlsRefs[index] = node;
938
setControlsRefs(controlsRefs);
939
};
940
941
const controls = Array(3).fill(0).map((_, index) => (
942
<UnstyledButton
943
key={index}
944
ref={setControlRef(index)}
945
onClick={() => setActive(index)}
946
mod={{ active: active === index }}
947
>
948
Tab {index + 1}
949
</UnstyledButton>
950
));
951
952
return (
953
<div ref={setRootRef}>
954
<FloatingIndicator
955
target={controlsRefs[active]}
956
parent={rootRef}
957
className="indicator"
958
/>
959
{controls}
960
</div>
961
);
962
}
963
```
964
965
## Theme Integration
966
967
Overlay components integrate with the Mantine theme system:
968
969
```tsx
970
import { MantineProvider } from '@mantine/core';
971
972
const theme = {
973
components: {
974
Modal: {
975
defaultProps: {
976
centered: true,
977
radius: 'md',
978
overlayProps: { backgroundOpacity: 0.55, blur: 3 },
979
},
980
},
981
Popover: {
982
defaultProps: {
983
position: 'bottom',
984
withArrow: true,
985
shadow: 'md',
986
},
987
},
988
Tooltip: {
989
defaultProps: {
990
withArrow: true,
991
color: 'dark',
992
},
993
},
994
},
995
};
996
```
997
998
## Accessibility Features
999
1000
Overlay components include comprehensive accessibility support:
1001
1002
- **Focus Management**: Proper focus trapping and restoration
1003
- **Keyboard Navigation**: Full keyboard support with arrow keys and Enter/Escape
1004
- **ARIA Attributes**: Proper labeling and roles for screen readers
1005
- **Screen Reader Support**: Announcements for state changes
1006
- **Color Contrast**: Sufficient contrast ratios for overlays and content