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 breakPoints={[0.75]}>
{(props) => (
<>
<Drawer.Trigger class="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="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-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-400" />
<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>
)
}
export default DrawerExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
import './index.css'
import Drawer from '@corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerExample: VoidComponent = () => {
return (
<Drawer 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>
<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>
)
}
export default DrawerExample
[data-corvu-drawer-trigger] {
border-width: 0;
cursor: pointer;
margin-top: auto;
margin-bottom: auto;
border-radius: 0.5rem;
background-color: hsl(249, 87%, 94%);
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 500;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 100ms;
animation-duration: 100ms;
}
[data-corvu-drawer-trigger]:hover {
background-color: hsl(251, 86%, 89%);
}
[data-corvu-drawer-trigger]:active {
transform: translate(0px, 0.125rem);
}
[data-corvu-drawer-overlay] {
position: fixed;
inset: 0px;
z-index: 40;
}
[data-corvu-drawer-overlay][data-transitioning] {
transition-property: background-color;
transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
transition-duration: 500ms;
}
[data-corvu-drawer-content] {
box-sizing: border-box;
border-width: 0;
border-style: solid;
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;
border-color: hsl(258, 79%, 74%);
background-color: hsl(249, 87%, 94%);
padding-top: 0.75rem;
}
[data-corvu-drawer-content]::after {
position: absolute;
left: 0px;
right: 0px;
top: 100%;
height: 50%;
background-color: inherit;
content: '';
}
[data-corvu-drawer-content][data-transitioning] {
transition-property: transform;
transition-timing-function: cubic-bezier(0.32, 0.72, 0, 1);
transition-duration: 500ms;
}
@media (min-width: 768px) {
[data-corvu-drawer-content] {
-webkit-user-select: none;
user-select: none;
}
}
.notch {
height: 0.25rem;
width: 2.5rem;
align-self: center;
border-radius: 9999px;
background-color: hsl(258, 79%, 74%);
}
[data-corvu-drawer-label] {
margin-top: 0.5rem;
margin-bottom: 0px;
text-align: center;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 700;
}
[data-corvu-drawer-description] {
margin-top: 0.25rem;
text-align: center;
}
.hidden_frog {
position: absolute;
left: 0px;
right: 0px;
bottom: -1.25rem;
z-index: 10;
text-align: center;
margin: 0px;
}
Features Section titled 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
Installation Section titled Installation
npm install @corvu/drawer
The drawer is also included in the main corvu
package under corvu/drawer
.
Usage Section titled Usage
import Drawer from '@corvu/drawer' // 'corvu/drawer'
// or
// import { Root, Trigger, ... } from '@corvu/drawer'
Anatomy Section titled Anatomy
<Drawer>
<Drawer.Trigger />
<Drawer.Portal>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Close />
<Drawer.Label />
<Drawer.Description />
</Drawer.Content>
</Drawer.Portal>
</Drawer>
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 Section titled 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 callback and use it to animate the background for example:
<Drawer>
{(props) => (
<Drawer.Overlay
class="fixed inset-0 z-40 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
)}
</Drawer>
Snap- and breakpoints Section titled 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 snapPoints={[0, 0.5, 1]}>
...
</Drawer>
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 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="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="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-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-400" />
<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>
)
}
export default DrawerSnapPointExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
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 breakPoints={[0.75]}>
...
</Drawer>
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 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="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="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-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-400" />
<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>
)
}
export default DrawerBreakPointExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
Scrollable elements Section titled 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 { For, type VoidComponent } from 'solid-js'
import clsx from 'clsx'
import Drawer from '@corvu/drawer'
const DrawerScrollableExample: VoidComponent = () => {
return (
<Drawer>
{(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="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="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-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:select-none">
<div class="h-1 w-10 shrink-0 self-center rounded-full bg-corvu-400" />
<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-200': idx() % 2 === 0,
})}
>
List item {idx() + 1}
</p>
)}
</For>
</div>
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer>
)
}
export default DrawerScrollableExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
Transitioning the content height Section titled 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 { createSignal, type VoidComponent } from 'solid-js'
import Drawer from '@corvu/drawer'
const heightSequence = [500, 400, 300, 400]
const DrawerTransitionResizeExample: VoidComponent = () => {
const [currentHeight, setCurrentHeight] = createSignal(400)
return (
<Drawer transitionResize>
{(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">
Transition resize example
</p>
<Drawer.Trigger class="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-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 fixed inset-x-0 bottom-0 z-50 flex flex-col rounded-t-lg border-t-4 border-corvu-400 bg-corvu-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-[transform,height] corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-400" />
<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 !== undefined) {
setCurrentHeight(nextHeight)
heightSequence.push(nextHeight)
}
}}
class="mx-auto mt-2 rounded-md bg-corvu-100 px-3 py-2 font-medium"
>
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>
)
}
export default DrawerTransitionResizeExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
Dynamic content height Section titled 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 { For, type VoidComponent } from 'solid-js'
import Drawer from '@corvu/drawer'
const DrawerCommentsExample: VoidComponent = () => {
return (
<Drawer 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="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-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 fixed inset-x-0 bottom-0 z-50 h-full max-h-[95%] rounded-t-lg border-t-4 border-corvu-400 bg-corvu-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md: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 self-center rounded-full bg-corvu-400" />
<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-200" />
<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>
)
}
export default DrawerCommentsExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
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 Section titled 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>
{(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="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="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-100 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:select-none">
<div class="h-1 w-10 self-center rounded-full bg-corvu-400" />
<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>
)
}
export default DrawerNoDragExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
Persistent content Section titled Persistent content
To persist the content of the drawer even when it’s unmounted, you can use corvu’s createPersistent
utility. It will cache the content when the drawer is opened for the first time and preserve its state and HTML elements.
const PersistedDrawerContent = () => {
const persistedContent = createPersistent(() => {
return <input />
})
return (
<Drawer>
<Drawer.Trigger>
Open
</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Content>
{persistedContent()}
</Drawer.Content>
</Drawer.Portal>
</Drawer>
)
}
Give it a try! 🐦⬛
No drag example
import createPersistent from 'solid-persistent'
import Drawer from '@corvu/drawer'
import type { VoidComponent } from 'solid-js'
const DrawerNoDragExample: VoidComponent = () => {
const persistedContent = createPersistent(DrawerContent)
return (
<Drawer>
{(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="my-auto rounded-lg bg-corvu-100 px-4 py-3 text-lg font-medium 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-50 corvu-transitioning:transition-colors corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)]"
style={{
'background-color': `rgb(0 0 0 / ${
0.5 * props.openPercentage
})`,
}}
/>
<Drawer.Content class="fixed inset-x-0 bottom-0 z-50 flex h-full max-h-[80%] flex-col items-center rounded-t-lg border-t-4 border-corvu-400 bg-corvu-100 px-5 pt-3 after:absolute after:inset-x-0 after:top-[calc(100%-1px)] after:h-1/2 after:bg-inherit corvu-transitioning:transition-transform corvu-transitioning:duration-500 corvu-transitioning:ease-[cubic-bezier(0.32,0.72,0,1)] md:max-h-[500px] md:select-none">
{persistedContent()}
</Drawer.Content>
</Drawer.Portal>
</>
)}
</Drawer>
)
}
const DrawerContent = () => (
<>
<div class="h-1 w-10 self-center rounded-full bg-corvu-400" />
<Drawer.Label class="text-lg font-bold">
Persistent drawer content
</Drawer.Label>
<Drawer.Description class="mt-2 text-center">
This drawer will preserve the state inside the content,
<br /> even after it gets unmounted from the DOM.
<br /> Enter something in the input and reopen the drawer.
</Drawer.Description>
<input class="mt-4 rounded border border-corvu-200 bg-corvu-bg px-3 py-2 ring-2 ring-corvu-400 focus-visible:border focus-visible:border-corvu-200 focus-visible:ring-2 focus-visible:ring-corvu-400" />
<p class="mt-3 text-center text-sm">
💡 I'm an uncontrolled input, preserving my state because I never
rerender!
</p>
</>
)
export default DrawerNoDragExample
import animatePlugin from 'tailwindcss-animate'
import corvuPlugin from '@corvu/tailwind'
import formsPlugin from '@tailwindcss/forms'
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: {
extend: {
colors: {
corvu: {
bg: '#f3f1fe',
100: '#e6e2fd',
200: '#d4cbfb',
300: '#bcacf6',
400: '#a888f1',
text: '#180f24',
},
},
},
},
plugins: [animatePlugin, corvuPlugin, formsPlugin],
}
Accessibility Section titled Accessibility
Adheres to the Dialog WAI-ARIA design pattern and Alert Dialog WAI-ARIA design pattern.
API reference Section titled 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 callback also accepts all props of the dialog root callback function, which are documented in the Dialog API reference.
Context wrapper for the drawer. Is required for every drawer you create.
Props
Inherits <Dialog.Root />
Props.
Property | Default | Type/Description |
---|---|---|
snapPoints | [0, 1] | Size[] An array of points to snap to. Can be either percentages of the total drawer height or CSS pixel values. |
breakPoints | - | [Size | null] Optionally override the default breakpoints between snap points. This list has to be the length of snapPoints.length - 1 . Provide null for breakpoints you don't want to override. |
defaultSnapPoint | 1 | Size The point to snap to when the drawer opens. |
activeSnapPoint | - | Size The active snap point. |
onActiveSnapPointChange | - | (activeSnapPoint: Size) => void Callback fired when the active snap point changes. |
side | 'bottom' | Side The side of the viewport the drawer appears. Is used to properly calculate dragging. |
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 | 200 | number After how many milliseconds the cached distance used for the velocity function should reset. |
allowSkippingSnapPoints | true | boolean Whether the user can skip snap points if the velocity is high enough. |
handleScrollableElements | true | 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. |
transitionResize | false | boolean Whether the drawer should watch for size changes and apply a fixed width/height for transitions. |
Button that changes the open state of the drawer when clicked.
Props
Inherits <Dialog.Trigger />
Props.
Data attributes
Data attributes present on <Trigger />
components.
Property | Description |
---|---|
data-corvu-drawer-trigger | Present on every drawer trigger element. |
data-open | Present when the drawer is open. |
data-closed | Present when the drawer is closed. |
Portals its children at the end of the body element to ensure that the dialog always rendered on top.
Props
Inherits <Dialog.Portal />
Props.
Component which can be used to create a faded background. Can be animated.
Props
Inherits <Dialog.Overlay />
Props.
Data attributes
Data attributes present on <Overlay />
components.
Property | Description |
---|---|
data-corvu-drawer-overlay | Present on every drawer overlay 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 . |
Content of the drawer. Can be animated.
Props
Inherits <Dialog.Content />
Props.
Data attributes
Data attributes present on <Content />
components.
Property | Description |
---|---|
data-corvu-drawer-content | Present on every drawer 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 . |
Close button that changes the open state to false when clicked.
Props
Inherits <Dialog.Close />
Props.
Data attributes
Data attributes present on <Close />
components.
Property | Description |
---|---|
data-corvu-drawer-close | Present on every drawer close element. |
Label element to announce the drawer to accessibility tools.
Props
Inherits <Dialog.Label />
Props.
Data attributes
Data attributes present on <Label />
components.
Property | Description |
---|---|
data-corvu-drawer-label | Present on every drawer label element. |
Description element to announce the drawer to accessibility tools.
Props
Inherits <Dialog.Description />
Props.
Data attributes
Data attributes present on <Description />
components.
Property | Description |
---|---|
data-corvu-drawer-description | Present on every drawer description element. |
Context which exposes various properties to interact with the drawer. Optionally provide a contextId to access a keyed context.
Returns
Property | Type/Description |
---|---|
snapPoints | Accessor<Size[]> An array of points to snap to. Can be either percentages of the total drawer height or CSS pixel values. |
breakPoints | Accessor<[Size | null]> Breakpoints between snap points. |
defaultSnapPoint | Accessor<Size> The point to snap to when the drawer opens. |
activeSnapPoint | Accessor<Size> The active snap point. |
setActiveSnapPoint | (snapPoint: Size) => 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. |
Inherited from <Dialog.useContext />
.
Props that are passed to the Root component children callback.
Props
Inherits <Dialog.RootChildrenProps />
Props.
Property | Type/Description |
---|---|
snapPoints | Size[] An array of points to snap to. Can be either percentages of the total drawer height or CSS pixel values. |
breakPoints | [Size | null] Breakpoints between snap points. |
defaultSnapPoint | Size The point to snap to when the drawer opens. |
activeSnapPoint | Size The active snap point. |
setActiveSnapPoint | (snapPoint: Size) => 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. |
type Side = 'top' | 'right' | 'bottom' | 'left'
type Size = `${number}px` | number
Developed and designed by Jasmin