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.

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

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

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

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

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

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

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 primitives/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.

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