Drawer Components
Bottom sheet drawers with drag-to-dismiss, snap points, and smooth animations. Inspired by Vaul, built with native dialog element.
I recommend using this component if you want to provide a PWA (Progressive Web App) experience in your Rails application. Here's a guide you might find useful: Progressive Web Apps with Rails
Installation
1. Stimulus Controller Setup
Start by adding the following controller to your project:
This code is available to Pro users only.
// Hey curious person!
// You seem to be sneaking around the code...
// I hope you're enjoying the components!
// Have a great day!
class SecretMessage {
constructor() {
this.message = "Thanks for checking out Rails Blocks!";
this.compliment = "You're awesome!";
}
reveal() {
console.log(this.message);
return this.compliment;
}
}
const secret = new SecretMessage();
secret.reveal();
2. Custom CSS
Add these CSS classes to style your drawer components. The drawer uses existing modal/slideover CSS plus these specific styles:
This code is available to Pro users only.
/* Hey curious person! */
/* You seem to be sneaking around the code... */
/* I hope you're enjoying the components! */
/* Have a great day! */
.secret-message {
/* Thanks for checking out Rails Blocks! */
opacity: 0.7;
transform: scale(0.95);
filter: blur(2px);
}
.awesome-btn:hover {
/* You're awesome! */
animation: wiggle 0.5s ease-in-out;
}
Examples
User Settings Drawer
A drawer for viewing user settings.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Non-dismissible Drawer
A non-dismissible cookie consent drawer. Users must make a choice before closing.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Responsive Drawer
Adapts between drawer on mobile and modal on desktop for optimal user experience. This requires the modal component to be installed.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Scrollable Content with Snap Points
Demonstrates scrollable content and multiple snap points. Swipe up/down on the handle to change heights.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Revealing Content Drawer
Progressively reveals more information as the drawer is expanded. This is great for a "Google Maps" like experience.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Template Lazy Loading
Load drawer content from a template when it opens. Perfect for complex forms or content that isn't needed until user interaction.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Turbo Frame Lazy Loading
Load drawer content from the server using Turbo Frames. Ideal for dynamic content that needs to be fresh on each open.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
This code is available to Pro users only.
# Hey curious person!
# You seem to be sneaking around the code...
# I hope you're enjoying the components!
# Have a great day!
class SecretMessage
def initialize
@message = "Thanks for checking out Rails Blocks!"
@compliment = "You're awesome!"
end
def reveal
puts @message
@compliment
end
end
secret = SecretMessage.new
secret.reveal
This code is available to Pro users only.
# Hey curious person!
# You seem to be sneaking around the code...
# I hope you're enjoying the components!
# Have a great day!
class SecretMessage
def initialize
@message = "Thanks for checking out Rails Blocks!"
@compliment = "You're awesome!"
end
def reveal
puts @message
@compliment
end
end
secret = SecretMessage.new
secret.reveal
Configuration
Understanding Snap Points
Snap points define preset heights where the drawer will "snap" to when dragging. They create a stepped interaction pattern:
Pixel-based Snap Points
- •
"200px"
= drawer is exactly 200px tall - •
"360px"
= drawer is exactly 360px tall - • Good for consistent heights across devices
Percentage-based Snap Points
- •
"50%"
= drawer takes half the screen - •
"90%"
= drawer takes 90% of screen - • Adapts to different screen sizes
Auto Snap Points to add in an array
- •
"auto"
= drawer adjusts to content height - • Calculates natural content height dynamically
- • Capped at 90% of viewport height
The drawer component provides a bottom sheet interface with drag-to-dismiss, snap points, and smooth animations.
Basic Usage
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Async Loading
The drawer component supports two methods of lazy loading content to improve performance:
1. Template-based Lazy Loading
Use a <template>
element to define content that loads when the drawer opens:
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
2. Turbo Frame Lazy Loading
Load content from the server using Turbo Frames for dynamic content:
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
The server endpoint should return a <turbo-frame id="drawer-lazy-content">
with the content to load.
Revealing Content
Progressively reveal content as the drawer expands. Use the snapContent
target with a data-show-from-snap-point
attribute, which specifies the snap point index at which the content becomes visible.
This code is available to Pro users only.
<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Secret Message from Rails Blocks</title>
<style>
.secret-message {
font-family: 'Comic Sans MS', cursive;
text-align: center;
padding: 2rem;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.wiggle { animation: wiggle 0.5s ease-in-out infinite; }
@keyframes wiggle {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(1deg); }
75% { transform: rotate(-1deg); }
}
</style>
</head>
<body>
<div class="secret-message">
<h1>🎉 Hello, Code Detective! 🕵️</h1>
<p>Thanks for checking out Rails Blocks!</p>
<p>You're clearly someone who pays attention to details.</p>
<p>That's exactly the kind of developer we love!</p>
<div class="cta-section">
<button class="awesome-btn wiggle">You're awesome!</button>
<p><small>Seriously, keep being curious! 🚀</small></p>
</div>
<footer>
<p>Built with ❤️ by the Rails Blocks team</p>
<p>Now go build something amazing!</p>
</footer>
</div>
<script>
console.log("🎊 Bonus points for opening the console!");
console.log("Keep exploring and happy coding! 💻");
</script>
</body>
</html>
Configuration Values
Value | Description | Type | Default |
---|---|---|---|
open
|
Whether the drawer is open on initial load |
Boolean
|
false
|
snapPoints
|
Array of snap point positions (px or %) |
Array
|
["180px", "80%"]
|
activeSnapPoint
|
Index of the active snap point |
Number
|
0
|
dismissible
|
Whether the drawer can be dismissed by dragging |
Boolean
|
true
|
fadeFromIndex
|
Snap point index to start fading the backdrop (-1 disables) |
Number
|
-1
|
closeThreshold
|
Percentage of drawer height to trigger close |
Number
|
0.25
|
velocityThreshold
|
Velocity threshold for flick gestures |
Number
|
0.5
|
lazyLoad
|
Whether to lazy load the drawer content |
Boolean
|
false
|
turboFrameSrc
|
URL for Turbo Frame lazy loading |
String
|
""
|
Targets
Target | Description | Required |
---|---|---|
dialog
|
The dialog element containing the drawer | Required |
handle
|
The drag handle element | Optional |
content
|
The content container element | Optional |
snapIndicator
|
Element to display current snap point value | Optional |
snapContent
|
Content that reveals at a specific snap point. Use with `data-show-from-snap-point`. | Optional |
template
|
Template element for lazy loading content | Optional |
Actions
Action | Description | Usage |
---|---|---|
show
|
Opens the drawer |
data-action="drawer#show:prevent"
|
hide
|
Closes the drawer |
data-action="drawer#hide:prevent"
|
backdropClose
|
Closes when clicking the backdrop |
data-action="mousedown->drawer#backdropClose"
|
snapToNext
|
Snaps to the next snap point |
data-action="drawer#snapToNext"
|
snapToPrevious
|
Snaps to the previous snap point |
data-action="drawer#snapToPrevious"
|
Key Features
- Drag to Dismiss: Natural drag gestures with velocity detection for smooth closing
- Snap Points: Multiple height positions with smooth transitions between them
- Touch & Mouse Support: Works on both mobile and desktop devices
- Fade Effect: Optional backdrop fade as drawer expands
- Non-dismissible Mode: Option to prevent drag dismissal for critical content
- Body Scroll Lock: Prevents background scrolling when drawer is open
- Turbo Integration: Automatically closes before page caching
Usage Notes
- The drawer handle is optional but recommended for better UX
- Snap points can be defined in pixels (e.g., "200px") or percentages (e.g., "50%")
- The drawer automatically handles overscroll behavior to prevent bouncing
- For responsive designs, consider using the drawer on mobile and modal on desktop
- The fade effect creates a nice visual hierarchy when expanding to full height