Drawer
A drawer is a draggable dialog that is attached to any side of the viewport. It uses the Dialog primitive under the hood and adds dragging logic on top of it.
import Drawer from 'corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerExample: VoidComponent = () => {
return (
<Drawer.Root breakPoints={[0.75]}>
{(props) => (
<>
<Drawer.Trigger class="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="peer fixed inset-x-0 bottom-0 z-50 flex h-full max-h-[500px] flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="mt-2 text-center text-xl font-bold">
I'm a drawer!
</Drawer.Label>
<Drawer.Description class="mt-1 text-center">
Drag down to close me.
</Drawer.Description>
<p class="absolute inset-x-0 -bottom-5 z-10 text-center">
đ¸ You found froggy!
</p>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
import Drawer from 'corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerExample: VoidComponent = () => {
return (
<Drawer.Root breakPoints={[0.75]}>
{(props) => (
<>
<Drawer.Trigger>Open Drawer</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="peer">
<div class="notch" />
<Drawer.Label>I'm a drawer!</Drawer.Label>
<Drawer.Description>Drag down to close me.</Drawer.Description>
<p class="hidden_frog">đ¸ You found froggy!</p>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerExample
[data-corvu-dialog-trigger] {
border-radius: 0.5rem;
background-color: rgb(230 226 253);
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
margin-top: auto;
margin-bottom: auto;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 500;
color: rgb(24 15 35);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
transition-duration: 100ms;
animation-duration: 100ms;
}
[data-corvu-dialog-trigger]:hover {
background-color: rgb(212 203 251);
}
[data-corvu-dialog-trigger]:active {
transform: translate(0px, 0.125rem);
}
[data-corvu-dialog-overlay] {
position: fixed;
inset: 0px;
z-index: 40;
}
.peer[data-transitioning]~[data-corvu-dialog-overlay] {
transition-property: background-color;
transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
transition-duration: 500ms;
}
[data-corvu-dialog-content] {
position: fixed;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 50;
display: flex;
height: 100%;
max-height: 500px;
flex-direction: column;
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
border-top-width: 4px;
padding-top: 0.75rem;
}
[data-corvu-dialog-content][data-transitioning] {
transition-property: transform;
transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
transition-duration: 500ms;
}
[data-corvu-dialog-content]::after {
position: absolute;
left: 0px;
right: 0px;
top: 100%;
height: 50%;
background-color: inherit;
}
@media (min-width: 1024px) {
[data-corvu-dialog-content] {
-webkit-user-select: none;
user-select: none;
}
}
[data-corvu-dialog-label] {
margin-top: 0.5rem;
text-align: center;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 700;
}
[data-corvu-dialog-description] {
margin-top: 1rem;
text-align: center;
}
.notch {
margin-top: 0.5rem;
height: 0.25rem;
width: 2.5rem;
align-self: center;
border-radius: 9999px;
}
.hidden_frog {
position: absolute;
left: 0px;
right: 0px;
bottom: -1.25rem;
z-index: 10;
text-align: center;
}
Features
- Attach to any side (top, right, bottom, left)
- Works with CSS transitions
- Custom snap- and breakpoints
- Handles scrollable content
- Customizable damping and velocity settings
Usage
import Drawer from 'corvu/drawer'
// Or
// import { Root, Trigger, ... } from 'corvu/drawer'
Anatomy
<Drawer.Root>
<Drawer.Trigger />
<Drawer.Portal>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Close />
<Drawer.Label />
<Drawer.Description />
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
Every component besides the Root
and Content
components are just re-exports from the Dialog primitive. You can find more information about them in the Dialog documentation.
Animating the drawer
The drawer content gets the data-transitioning
data attribute applied when it is transitioning. Transitioning means that the drawer is either opening, closing or moving to a snap point after the user stops dragging.
Animation is done by applying CSS transition properties when the drawer is in this transitioning
state. You can use any transition timing function, duration and delay you want. The drawer will automatically apply the data-transitioning
attribute to the Content
component for the duration of the transition and remove it when it is done.
A plain CSS example would look like this:
[data-corvu-dialog-content][data-transitioning] {
transition-property: transform;
transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
transition-duration: 500ms;
}
Corvuâs tailwind plugin also provides a corvu-transitioning
modifier that you can use:
<Drawer.Content
class="
corvu-transitioning:transition-transform
corvu-transitioning:duration-500
"
>
</Drawer.Content>
Additionally, there are different data attributes applied depending on the current state of the drawer:
data-opening
: Present when the drawer is in the open transition.data-closing
: Present when the drawer is in the close transition.data-snapping
: Present when the drawer is transitioning after the user stops dragging.
This allows you to apply different transitions based on the transition state of the drawer.
You can also use the openPercentage
property returned by the Drawer.useContext()
or the root children function and use it to animate the background for example:
<Drawer.Root>
{(props) => (
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
)}
</Drawer.Root>
Snap- and breakpoints
The drawer root accepts a snapPoints
array to customize the points to snap to. Valid values are either percentages or pixel values. The drawer will snap to the closest snap point when the user stops dragging (With the velocityFunction
in mind. See the API Reference for more information)
<Drawer.Root snapPoints={[0, 0.5, 1]}>
...
</Drawer.Root>
Here, the drawer additionally snaps to 50% of its height.
Snappoints example
import Drawer from 'corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerSnapPointExample: VoidComponent = () => {
return (
<Drawer.Root snapPoints={[0, 0.5, 1]} allowSkippingSnapPoints={false}>
{(props) => (
<>
<div class="my-auto flex flex-col items-center">
<p class="mb-2 rounded-lg bg-corvu-300 px-2 py-1 font-bold">
Snappoints example
</p>
<Drawer.Trigger class="rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
</div>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="peer fixed inset-x-0 bottom-0 z-50 flex h-full max-h-[500px] flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="mt-2 text-center text-xl font-bold">
I'm a drawer!
</Drawer.Label>
<Drawer.Description class="mt-1 text-center">
I will snap at <span class="font-bold">50%</span> of my height.{' '}
<br /> My current height is:{' '}
<span class="font-bold">
{(props.openPercentage * 100).toFixed(2)}%
</span>
</Drawer.Description>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerSnapPointExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
Per default, the drawer will snap to the closest snap point. You can change this behavior by providing custom breakPoints
to the root component:
<Drawer.Root breakPoints={[0.75]}>
...
</Drawer.Root>
Here, the drawer will close when the user drags below 75% of the drawerâs height.
Breakpoints example
import Drawer from 'corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerBreakPointExample: VoidComponent = () => {
return (
<Drawer.Root breakPoints={[0.75]} velocityFunction={() => 1}>
{(props) => (
<>
<div class="my-auto flex flex-col items-center">
<p class="mb-2 rounded-lg bg-corvu-300 px-2 py-1 font-bold">
Breakpoints example
</p>
<Drawer.Trigger class="rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
</div>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="peer fixed inset-x-0 bottom-0 z-50 flex h-full max-h-[500px] flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="mt-2 text-center text-xl font-bold">
I'm a drawer!
</Drawer.Label>
<Drawer.Description class="mt-1 text-center">
I will close when I'm under <span class="font-bold">75%</span>{' '}
of my height. <br /> My current height is:{' '}
<span class="font-bold">
{(props.openPercentage * 100).toFixed(2)}%
</span>
</Drawer.Description>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerBreakPointExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
Scrollable elements
Scrollable elements work out of the box. The drawer will check if the user is dragging on a scrollable element and handle dragging properly.
Scrollable example
import clsx from 'clsx'
import Drawer from 'corvu/drawer'
import { For, type VoidComponent } from 'solid-js'
const DrawerScrollableExample: VoidComponent = () => {
return (
<Drawer.Root>
{(props) => (
<>
<div class="my-auto flex flex-col items-center">
<p class="mb-2 rounded-lg bg-corvu-300 px-2 py-1 font-bold">
Scrollable example
</p>
<Drawer.Trigger class="rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
</div>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="peer fixed inset-x-0 bottom-0 z-50 flex h-full max-h-[500px] flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div class="h-1 w-10 shrink-0 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="mt-2 text-center text-xl font-bold">
Drawer with a scrollable element
</Drawer.Label>
<div class="mt-3 grow divide-y divide-corvu-400 overflow-y-auto">
<For each={new Array(20)}>
{(_, idx) => (
<p
class={clsx('py-2 text-center font-bold', {
'bg-corvu-dark': idx() % 2 === 0,
})}
>
List item {idx() + 1}
</p>
)}
</For>
</div>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerScrollableExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
Transitioning the content height
Corvuâs drawer can handle height: auto
and transition between heights when the content changes. This is possible by using a ResizeObserver
to detect height changes and apply a fixed height for the time of the transition.
This is disabled by default and you need to set the transitionResize
property on the root component to enable it. Also, remember to set transition-property: height
on the drawer content ;).
Transition resize example
import Drawer from 'corvu/drawer'
import { createSignal, type VoidComponent } from 'solid-js'
const heightSequence = [500, 400, 300, 400]
const DrawerTransitionResizeExample: VoidComponent = () => {
const [currentHeight, setCurrentHeight] = createSignal(400)
return (
<Drawer.Root transitionResize>
{(props) => (
<>
<p class="mb-2 rounded-lg bg-corvu-300 px-2 py-1 font-bold">
Transition resize example
</p>
<Drawer.Trigger class="rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="group peer fixed inset-x-0 bottom-0 z-50 flex flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-[transform,height] corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="mt-2 text-center text-xl font-bold">
Dynamic content height example
</Drawer.Label>
<Drawer.Description class="mt-1 text-center">
I will transition between height changes
</Drawer.Description>
<button
onClick={() => {
const nextHeight = heightSequence.shift()
if (nextHeight) {
setCurrentHeight(nextHeight)
heightSequence.push(nextHeight)
}
}}
class="mx-auto mt-2 rounded-md bg-corvu-100 px-3 py-2 font-medium text-corvu-dark"
>
Resize content
</button>
<div
class="mx-5 mb-5 mt-3 flex items-center justify-center rounded-lg border-2 border-corvu-400 corvu-group-resizing:grow"
style={{
height: `${currentHeight()}px`,
}}
>
<p class="text-2xl">đ</p>
</div>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerTransitionResizeExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
Dynamic content height
You might want the height of the drawer to adjust so its content is always fully visible no matter what snap point the user is on.
A common example would be a comment section which can be expanded:
Comments example
import Drawer from 'corvu/drawer'
import { For, type VoidComponent } from 'solid-js'
const DrawerCommentsExample: VoidComponent = () => {
return (
<Drawer.Root snapPoints={[0, 0.7, 1]} allowSkippingSnapPoints={false}>
{(props) => (
<>
<div class="my-auto flex flex-col items-center">
<p class="mb-2 rounded-lg bg-corvu-300 px-2 py-1 font-bold">
Comments example
</p>
<Drawer.Trigger class="rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
</div>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="group peer fixed inset-x-0 bottom-0 z-50 h-full max-h-[95%] rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div
class="flex flex-col corvu-group-transitioning:transition-[height] corvu-group-transitioning:duration-500 corvu-group-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
height: `${
props.openPercentage < 0.7 ? 70 : props.openPercentage * 100
}%`,
}}
>
<div class="h-1 w-10 shrink-0 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="border-b-2 border-corvu-400 py-2 text-center text-xl font-bold">
Comments
</Drawer.Label>
<div class="grow divide-y divide-white/10 overflow-y-auto">
<For each={new Array(20)}>
{() => (
<div class="flex items-center space-x-2 p-2">
<div class="size-8 rounded-full bg-corvu-800" />
<div>
<p class="font-bold">Username</p>
<p class="text-sm">This is a comment</p>
</div>
</div>
)}
</For>
</div>
<div class="z-10 border-t-2 border-corvu-400 p-2 text-center text-lg font-bold">
Comments Footer
</div>
</div>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerCommentsExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
To achieve this, you can use the props.openPercentage
property and change the height of the drawer accordingly. Make sure to create a wrapper element inside Drawer.Content
and not apply the height directly to the Content
component, otherwise the drawer will not be able to calculate the correct height.
style={{
height: `${
props.openPercentage < 0.7
? 70
: props.openPercentage * 100
}%`,
}}
The height will adjust to match the drawerâs height until itâs under 70%, which is the last snap point. Donât forget to apply the same transition function as you defined for your drawer transform and youâre good to go!
Disable dragging on certain elements
You can disable drag on an element by giving it the data-corvu-no-drag
attribute. Corvu will ignore any drag events on elements with this attribute.
No drag example
import Drawer from 'corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerNoDragExample: VoidComponent = () => {
return (
<Drawer.Root>
{(props) => (
<>
<div class="my-auto flex flex-col items-center">
<p class="mb-2 rounded-lg bg-corvu-300 px-2 py-1 font-bold">
No drag example
</p>
<Drawer.Trigger class="rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium text-corvu-dark transition-all duration-100 hover:bg-corvu-200 active:translate-y-0.5">
Open Drawer
</Drawer.Trigger>
</div>
<Drawer.Portal>
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-peer-transitioning:transition-colors corvu-peer-transitioning:duration-500 corvu-peer-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="peer fixed inset-x-0 bottom-0 z-50 flex h-full max-h-[500px] flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-1000 pt-3 after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] lg:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-50" />
<Drawer.Label class="mt-2 text-center text-xl font-bold">
I'm a drawer!
</Drawer.Label>
<div
class="m-6 flex h-[200px] items-center justify-center rounded-lg border-2 border-corvu-400 text-center text-lg"
data-corvu-no-drag
>
Dragging in here does nothing.
<br />
Have a cookie đŞ
</div>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer.Root>
)
}
export default DrawerNoDragExample
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
50: '#f2f0fe',
100: '#e6e2fd',
200: '#d4cbfb',
light: '#D4C0FF',
300: '#bcacf6',
400: '#a888f1',
500: '#9a6de9',
600: '#8f50dc',
700: '#7e41c3',
accent: '#7250AE',
800: '#63359c',
900: '#52317d',
dark: '#180f23',
1000: '#0C0812',
},
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('tailwindcss-animate'),
require('@corvu/tailwind'),
],
}
Accessibility
Adheres to the Dialog WAI-ARIA design pattern and Alert Dialog WAI-ARIA design pattern.
API reference
Only components which are specific to the drawer are documented here. For all other components, please refer to the Dialog API reference.
The dialog context is re-exported as Drawer.useDialogContext
and the root children function also accepts all props of the dialog root children function, which are documented in the Dialog API reference.
Drawer.Root
Component
Context wrapper for the drawer. Is required for every drawer you create.
Props
snapPoints
[string | number]
An array of points to snap to. Can be either percentages of the total drawer height or CSS pixel values. *Default = [0, 1]
*
breakPoints
[string | number | DefaultBreakpoint]
Optionally override the default breakpoints between snap points. This list has to be the length of snapPoints.length - 1
. Use Drawer.DefaultBreakpoint
or undefined
for breakpoints you don't want to override. *Default = [Drawer.DefaultBreakpoint]
*
defaultSnapPoint
string | number
The point to snap to when the drawer opens. *Default = 1
*
activeSnapPoint
string | number
The active snap point.
onActiveSnapPointChange
(activeSnapPoint: string | number) => void
Callback fired when the active snap point changes.
side
Side
The side of the viewport the drawer appears. Is used to properly calculate dragging. *Default = 'bottom'
*
dampFunction
(distance: number) => number
Function to create a dampened distance if the user tries to drag the drawer away from the last snap point.
velocityFunction
(distance: number, time: number) => number
Function to calculate the velocity when the user stop dragging. This velocity modifier is used to calculate the point the drawer will snap to after release. You can disable velocity by always returning 1.
velocityCacheReset
number
After how many milliseconds the cached distance used for the velocity function should reset. *Default = 200
*
allowSkippingSnapPoints
boolean
Whether the user can skip snap points if the velocity is high enough. *Default = true
*
handleScrollableElements
boolean
Corvu drawers have logic to make dragging and scrolling work together. If you don't want this behavior or if you want to implement something yourself, you can disable it with this property. *Default = true
*
transitionResize
boolean
Whether the drawer should watch for size changes and apply a fixed width/height for transitions. *Default = false
*
children
JSX.Element | (props: DrawerRootChildrenProps & DialogRootChildrenProps) => JSX.Element
Drawer.Content
Component
Content of the drawer. Can be animated.
Props
as
ValidComponent
Default: div
Component to render the polymorphic component as. *Default = div
*
asChild
boolean
Whether to render the polymorphic component as the first <âAs />
component found in its children. *Default = false
*
forceMount
boolean
Whether the dialog content should be forced to render. Useful when using third-party animation libraries.
contextId
string
The id
of the dialog context to use.
Data
Data attributes present on Drawer.Content components.
data-corvu-dialog-content
Present on every drawer/dialog content element.
data-open
Present when the drawer is open.
data-closed
Present when the drawer is closed.
data-transitioning
Present when the drawer is transitioning (opening, closing or snapping).
data-opening
Present when the drawer is in the open transition.
data-closing
Present when the drawer is in the close transition.
data-snapping
Present when the drawer is transitioning after the user stops dragging.
data-resizing
Present when the drawer is transitioning after the size (width/height) changes. Only present if transitionResize
is set to true
.
CSS props
CSS properties attributes present on Drawer.Content components.
Drawer.useContext
Context
Context which exposes various properties to interact with the drawer. Optionally provide a contextId to access a keyed context.
Returns
snapPoints
Accessor<[string | number]>
An array of points to snap to. Can be either percentages of the total drawer height or CSS pixel values.
breakPoints
Accessor<[string | number | DefaultBreakpoint]>
Breakpoints between snap points.
defaultSnapPoint
Accessor<string | number>
The point to snap to when the drawer opens.
activeSnapPoint
Accessor<string | number>
The active snap point.
setActiveSnapPoint
(snapPoint: string | number) => void
Change the active snap point.
side
Accessor<Side>
The side of the viewport the drawer appears. Is used to properly calculate dragging.
isDragging
Accessor<boolean>
Whether the drawer is currently being dragged by the user.
isTransitioning
Accessor<boolean>
Whether the drawer is currently transitioning to a snap point after the user stopped dragging or the drawer opens/closes.
transitionState
Accessor<'opening' | 'closing' | 'snapping' | 'resizing' | null>
The transition state that the drawer is currently in.
openPercentage
Accessor<number>
How much the drawer is currently open. Can be > 1 depending on your dampFunction
.
translate
Accessor<number>
The current translate value applied to the drawer. Is the same for every side.
velocityCacheReset
Accessor<number>
After how many milliseconds the cached distance used for the velocity function should reset.
allowSkippingSnapPoints
Accessor<boolean>
Whether the user can skip snap points if the velocity is high enough.
handleScrollableElements
Accessor<boolean>
Whether the logic to handle dragging on scrollable elements is enabled.
transitionResize
Accessor<boolean>
Whether the drawer watches for size changes and applies a fixed width/height for transitions.
Drawer.RootChildrenProps
Type
Props which are passed to the Root component children function.
Props
snapPoints
[string | number]
An array of points to snap to. Can be either percentages of the total drawer height or CSS pixel values.
breakPoints
[string | number | DefaultBreakpoint]
Breakpoints between snap points.
defaultSnapPoint
string | number
The point to snap to when the drawer opens.
activeSnapPoint
string | number
The active snap point.
setActiveSnapPoint
(snapPoint: string | number) => void
Set the current active snap point.
side
Side
The side of the viewport the drawer appears. Is used to properly calculate dragging.
isDragging
boolean
Whether the drawer is currently being dragged by the user.
isTransitioning
boolean
Whether the drawer is currently transitioning to a snap point after the user stopped dragging or the drawer opens/closes.
transitionState
'opening' | 'closing' | 'snapping' | 'resizing' | null
The transition state that the drawer is currently in.
openPercentage
number
How much the drawer is currently open. Can be > 1 depending on your dampFunction
.
translate
number
The current translate value applied to the drawer. Is the same for every side.
velocityCacheReset
number
After how many milliseconds the cached distance used for the velocity function should reset.
allowSkippingSnapPoints
boolean
Whether the user can skip snap points if the velocity is high enough.
handleScrollableElements
boolean
Whether the logic to handle dragging on scrollable elements is enabled.
transitionResize
boolean
Whether the drawer watches for size changes and applies a fixed width/height for transitions.
corvu@0.2.3
Developed and designed by Jasmin