{ "$schema": "../schemas/code-crispies-module-schema.json", "id": "css-carousels", "title": "Carousels", "description": "Learn to create modern, accessible carousels using pure CSS with scroll snapping, scroll buttons, and scroll markers. This module covers advanced CSS features that enable interactive carousel components without JavaScript, focusing on responsive design and user accessibility.", "difficulty": "intermediate", "lessons": [ { "id": "basic-horizontal-scroll", "title": "Scroll Container", "description": "Before building carousels, we need to understand how to create horizontally scrolling containers. A scroll container is created by setting the overflow-x property to scroll on a container element. This allows content that exceeds the container's width to be scrolled horizontally instead of breaking to new lines or being hidden.", "task": "Create a horizontal scroll container by setting overflow-x: scroll on the .container element. This will allow the wide content inside to scroll horizontally.", "previewHTML": "
\n
Item 1
\n
Item 2
\n
Item 3
\n
Item 4
\n
Item 5
\n
", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.container { width: 300px; border: 2px solid #333; padding: 10px; }\n.item { display: inline-block; width: 120px; height: 80px; background: lightblue; margin-right: 10px; text-align: center; line-height: 80px; border-radius: 4px; }", "sandboxCSS": "", "codePrefix": "/* Make the container scroll horizontally */\n.container {\n ", "initialCode": "", "codeSuffix": "\n}", "previewContainer": "preview-area", "solution": "overflow-x: scroll;", "validations": [ { "type": "contains", "value": "overflow-x:", "message": "Use the overflow-x property to control horizontal scrolling" }, { "type": "contains", "value": "scroll", "message": "Set the value to scroll to enable horizontal scrolling" }, { "type": "property_value", "value": { "property": "overflow-x", "expected": "scroll" }, "message": "Use overflow-x: scroll to make the container scrollable horizontally" } ] }, { "id": "scroll-snap-introduction", "title": "Scroll Snap", "description": "Scroll snapping makes carousels feel more polished by ensuring that items align properly after scrolling. The scroll-snap-type property is applied to the scroll container, while scroll-snap-align is applied to the items inside. The x mandatory value means horizontal snapping is required.", "task": "Add scroll snapping to the container by setting scroll-snap-type: x mandatory. Then add scroll-snap-align: center to the items so they snap to the center of the container.", "previewHTML": "
\n
Item 1
\n
Item 2
\n
Item 3
\n
Item 4
\n
Item 5
\n
\n

Try scrolling in the container above - items should snap into place!

", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.container { width: 300px; height: 120px; border: 2px solid #333; padding: 10px; overflow-x: scroll; display: flex; gap: 10px; }\n.item { flex: 0 0 120px; height: 80px; background: lightcoral; text-align: center; line-height: 80px; border-radius: 4px; }", "sandboxCSS": "", "codePrefix": "/* Add scroll snapping to the container */\n.container {\n ", "initialCode": "", "codeSuffix": "\n}\n\n/* Make items snap to center */\n.item {\n \n}", "previewContainer": "preview-area", "solution": "scroll-snap-type: x mandatory;\n}\n\n.item {\n scroll-snap-align: center;", "validations": [ { "type": "contains", "value": "scroll-snap-type:", "message": "Add the scroll-snap-type property to the container" }, { "type": "contains", "value": "x mandatory", "message": "Use x mandatory for horizontal mandatory snapping" }, { "type": "contains", "value": "scroll-snap-align:", "message": "Add scroll-snap-align to the items" }, { "type": "contains", "value": "center", "message": "Use center to align items to the center of the container" } ] }, { "id": "flexbox-carousel-layout", "title": "Full-Width Items", "description": "For carousels where each item takes the full width (like image slideshows), we use flexbox with flex: 0 0 100% on each item. This means: no grow (0), no shrink (0), and take 100% of the container width. Combined with a horizontal gap, this creates distinct pages.", "task": "Create a full-width carousel layout using flexbox. Set display: flex and gap: 20px on the container, then flex: 0 0 100% on the items.", "previewHTML": "
\n
Slide 1
\n
Slide 2
\n
Slide 3
\n
Slide 4
\n
", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.carousel { width: 100%; height: 200px; border: 2px solid #333; overflow-x: scroll; scroll-snap-type: x mandatory; }\n.slide { height: 180px; background: linear-gradient(45deg, #ff6b6b, #4ecdc4); color: white; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; scroll-snap-align: center; }", "sandboxCSS": "", "codePrefix": "/* Set up flexbox layout for full-width slides */\n.carousel {\n ", "initialCode": "", "codeSuffix": "\n}\n\n/* Make each slide take full width */\n.slide {\n \n}", "previewContainer": "preview-area", "solution": "display: flex;\n gap: 20px;\n}\n\n.slide {\n flex: 0 0 100%;", "validations": [ { "type": "contains", "value": "display: flex", "message": "Use display: flex to create a flexbox layout" }, { "type": "contains", "value": "gap:", "message": "Add a gap property to create space between slides" }, { "type": "contains", "value": "20px", "message": "Set the gap to 20px" }, { "type": "contains", "value": "flex: 0 0 100%", "message": "Use flex: 0 0 100% to make each slide take full width" } ] }, { "id": "scroll-buttons-basic", "title": "Scroll Buttons", "description": "CSS scroll buttons are created using the ::scroll-button() pseudo-element. You specify the direction (left, right, up, down) and set content to make them visible. The browser automatically handles the scrolling behavior and disables buttons when they can't scroll further.", "task": "Add scroll buttons by creating ::scroll-button(left) and ::scroll-button(right) pseudo-elements. Set their content to arrow symbols and style them with a font size and color.", "previewHTML": "
\n
Slide 1
\n
Slide 2
\n
Slide 3
\n
Slide 4
\n
", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.carousel { width: 100%; height: 200px; border: 2px solid #333; overflow-x: scroll; scroll-snap-type: x mandatory; display: flex; gap: 20px; position: relative; }\n.slide { flex: 0 0 100%; height: 180px; background: linear-gradient(45deg, #667eea, #764ba2); color: white; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; scroll-snap-align: center; }", "sandboxCSS": "", "codePrefix": "/* Style all scroll buttons */\n.carousel::scroll-button(*) {\n font-size: 24px;\n color: #333;\n background: white;\n border: 2px solid #333;\n padding: 10px;\n cursor: pointer;\n}\n\n/* Create left scroll button */\n.carousel::scroll-button(left) {\n ", "initialCode": "", "codeSuffix": "\n}\n\n/* Create right scroll button */\n.carousel::scroll-button(right) {\n \n}", "previewContainer": "preview-area", "solution": "content: \"◀\";\n}\n\n.carousel::scroll-button(right) {\n content: \"▶\";", "validations": [ { "type": "contains", "value": "::scroll-button(left)", "message": "Create a left scroll button using ::scroll-button(left)" }, { "type": "contains", "value": "::scroll-button(right)", "message": "Create a right scroll button using ::scroll-button(right)" }, { "type": "contains", "value": "content:", "message": "Set the content property to display the button" }, { "type": "regex", "value": "content:\\s*[\"'][◀◄<][\"']", "message": "Use a left arrow symbol like ◀ or ◄ for the left button content" }, { "type": "regex", "value": "content:\\s*[\"'][▶►>][\"']", "message": "Use a right arrow symbol like ▶ or ► for the right button content" } ] }, { "id": "scroll-markers-introduction", "title": "Scroll Markers", "description": "Scroll markers are navigation dots that show your position in the carousel and allow jumping to specific slides. They're created using ::scroll-marker pseudo-elements on the slide items, and collected in a ::scroll-marker-group. The scroll-marker-group property must be set to after or before to enable them.", "task": "Enable scroll markers by setting scroll-marker-group: after on the carousel. Then style the markers using ::scroll-marker pseudo-elements with circular appearance.", "previewHTML": "
\n
Slide 1
\n
Slide 2
\n
Slide 3
\n
Slide 4
\n
", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.carousel { width: 100%; height: 200px; border: 2px solid #333; overflow-x: scroll; scroll-snap-type: x mandatory; display: flex; gap: 20px; }\n.slide { flex: 0 0 100%; height: 180px; background: linear-gradient(45deg, #f093fb, #f5576c); color: white; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; scroll-snap-align: center; }\n.carousel::scroll-button(*) { font-size: 20px; color: #333; background: white; border: 1px solid #333; padding: 8px; }\n.carousel::scroll-button(left) { content: \"◀\"; }\n.carousel::scroll-button(right) { content: \"▶\"; }", "sandboxCSS": "", "codePrefix": "/* Enable scroll markers */\n.carousel {\n ", "initialCode": "", "codeSuffix": "\n}\n\n/* Style the scroll markers */\n.slide::scroll-marker {\n \n}", "previewContainer": "preview-area", "solution": "scroll-marker-group: after;\n}\n\n.slide::scroll-marker {\n content: \"\";\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #ccc;\n border: 2px solid #333;", "validations": [ { "type": "contains", "value": "scroll-marker-group:", "message": "Add the scroll-marker-group property to enable markers" }, { "type": "contains", "value": "after", "message": "Set scroll-marker-group: after to place markers after the content" }, { "type": "contains", "value": "::scroll-marker", "message": "Use ::scroll-marker pseudo-element to style the markers" }, { "type": "contains", "value": "content:", "message": "Set content: \"\" to display the markers" }, { "type": "contains", "value": "border-radius:", "message": "Use border-radius to make circular markers" } ] }, { "id": "active-marker-styling", "title": "Active Marker", "description": "The :target-current pseudo-class automatically selects the scroll marker that corresponds to the currently visible slide. This is essential for user experience as it shows which slide is active. You can style it differently to indicate the current position.", "task": "Use the :target-current pseudo-class to highlight the active scroll marker. Change its background color to make it stand out from inactive markers.", "previewHTML": "
\n
Slide 1
\n
Slide 2
\n
Slide 3
\n
Slide 4
\n
\n

Scroll or click markers to see the active state change!

", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.carousel { width: 100%; height: 200px; border: 2px solid #333; overflow-x: scroll; scroll-snap-type: x mandatory; display: flex; gap: 20px; scroll-marker-group: after; }\n.slide { flex: 0 0 100%; height: 180px; background: linear-gradient(45deg, #a8edea, #fed6e3); color: #333; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; scroll-snap-align: center; }\n.slide::scroll-marker { content: \"\"; width: 12px; height: 12px; border-radius: 50%; background: #eee; border: 2px solid #333; }\n.carousel::scroll-button(*) { font-size: 18px; color: #333; background: white; border: 1px solid #333; padding: 6px; }\n.carousel::scroll-button(left) { content: \"◀\"; }\n.carousel::scroll-button(right) { content: \"▶\"; }", "sandboxCSS": "", "codePrefix": "/* Highlight the active/current scroll marker */\n.slide::scroll-marker:target-current {\n ", "initialCode": "", "codeSuffix": "\n}", "previewContainer": "preview-area", "solution": "background: #333;\n border-color: #666;", "validations": [ { "type": "contains", "value": ":target-current", "message": "Use the :target-current pseudo-class to target the active marker" }, { "type": "contains", "value": "background:", "message": "Change the background color to highlight the active marker" }, { "type": "regex", "value": "::scroll-marker:target-current", "message": "Apply :target-current to the ::scroll-marker pseudo-element" } ] }, { "id": "multi-column-carousel", "title": "Multi-Item", "description": "For responsive carousels showing multiple items per page, CSS multi-column layout is ideal. Using columns: 1 creates full-width columns, and items flow naturally into each column. The ::column pseudo-element represents each generated column and can have scroll-snap alignment.", "task": "Create a multi-item carousel using columns: 1 for the layout and scroll-snap-align: center on the ::column pseudo-elements to snap to each column.", "previewHTML": "
\n
Item 1
\n
Item 2
\n
Item 3
\n
Item 4
\n
Item 5
\n
Item 6
\n
Item 7
\n
Item 8
\n
", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; }\n.multi-carousel { width: 100%; height: 250px; border: 2px solid #333; padding: 10px; overflow-x: scroll; scroll-snap-type: x mandatory; text-align: center; }\n.item { display: inline-block; width: 150px; height: 100px; margin: 5px; background: linear-gradient(45deg, #667eea, #764ba2); color: white; line-height: 100px; text-align: center; border-radius: 8px; }", "sandboxCSS": "", "codePrefix": "/* Set up multi-column layout */\n.multi-carousel {\n ", "initialCode": "", "codeSuffix": "\n}\n\n/* Make columns snap to center */\n.multi-carousel::column {\n \n}", "previewContainer": "preview-area", "solution": "columns: 1;\n}\n\n.multi-carousel::column {\n scroll-snap-align: center;", "validations": [ { "type": "contains", "value": "columns:", "message": "Use the columns property to create column layout" }, { "type": "contains", "value": "columns: 1", "message": "Set columns: 1 to create full-width columns" }, { "type": "contains", "value": "::column", "message": "Use ::column pseudo-element to target generated columns" }, { "type": "contains", "value": "scroll-snap-align: center", "message": "Add scroll-snap-align: center to snap columns to center" } ] }, { "id": "complete-carousel-styling", "title": "Complete Carousel", "description": "A professional carousel combines all the features we've learned: scroll snapping, scroll buttons, scroll markers, and proper styling for different states. This includes hover effects, disabled states for buttons, and positioning for optimal user experience.", "task": "Complete the carousel by adding hover effects to buttons using :hover, styling disabled buttons with :disabled, and ensuring the marker group has proper flexbox layout.", "previewHTML": "
\n
🌅 Slide 1
\n
🌄 Slide 2
\n
🏔️ Slide 3
\n
🌊 Slide 4
\n
🌆 Slide 5
\n
", "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; background: #f5f5f5; }\n.complete-carousel { width: 100%; height: 250px; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow-x: scroll; scroll-snap-type: x mandatory; display: flex; gap: 0; scroll-marker-group: after; }\n.slide { flex: 0 0 100%; height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; align-items: center; justify-content: center; font-size: 36px; scroll-snap-align: center; }\n.slide:nth-child(2) { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }\n.slide:nth-child(3) { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }\n.slide:nth-child(4) { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); }\n.slide:nth-child(5) { background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); }\n.complete-carousel::scroll-button(*) { font-size: 20px; background: rgba(255,255,255,0.9); border: none; border-radius: 50%; width: 40px; height: 40px; cursor: pointer; }\n.complete-carousel::scroll-button(left) { content: \"◀\"; }\n.complete-carousel::scroll-button(right) { content: \"▶\"; }\n.complete-carousel::scroll-marker-group { display: flex; gap: 8px; justify-content: center; }\n.slide::scroll-marker { content: \"\"; width: 10px; height: 10px; border-radius: 50%; background: rgba(255,255,255,0.5); border: none; }\n.slide::scroll-marker:target-current { background: white; }", "sandboxCSS": "", "codePrefix": "/* Add hover effects to scroll buttons */\n.complete-carousel::scroll-button(*):hover {\n ", "initialCode": "", "codeSuffix": "\n}\n\n/* Style disabled scroll buttons */\n.complete-carousel::scroll-button(*):disabled {\n \n}", "previewContainer": "preview-area", "solution": "background: white;\n transform: scale(1.1);\n}\n\n.complete-carousel::scroll-button(*):disabled {\n opacity: 0.3;\n cursor: not-allowed;", "validations": [ { "type": "contains", "value": ":hover", "message": "Add :hover pseudo-class for button hover effects" }, { "type": "contains", "value": ":disabled", "message": "Add :disabled pseudo-class for disabled button styling" }, { "type": "contains", "value": "opacity:", "message": "Use opacity to show disabled state" }, { "type": "contains", "value": "cursor:", "message": "Change the cursor for better UX on disabled buttons" } ] } ] }