DEV Community

Cover image for Flexbox Hands-On: A Visual Playground for Learning CSS Layout
Learn Computer Academy
Learn Computer Academy

Posted on

Flexbox Hands-On: A Visual Playground for Learning CSS Layout

Are you tired of wrestling with CSS layouts? Flexbox offers powerful solutions, but there's a big difference between reading documentation and seeing it in action. That's why I created an interactive Flexbox Explorer that helps you master this essential layout technique through hands-on experimentation. ๐Ÿงช

Why Another Flexbox Tool?

Learning web development concepts clicks best when you can immediately see the results of your code changes. My Flexbox Explorer bridges theory and practice by letting you:

  • Tweak container properties and see instant layout changes
  • Style individual flex items with custom colors and content
  • Access the exact HTML/CSS code powering what you see
  • Physically rearrange items with drag-and-drop
  • Copy ready-to-use code for your projects

Try it yourself: https://playground.learncomputer.in/css-flexbox-playground/

The Explorer Interface

The tool features a three-part interface designed for optimal learning:

  1. Control Station - All your Flexbox properties in simple dropdown menus
  2. Live Preview - A visual sandbox showing your layout in real-time
  3. Code Display - The actual HTML and CSS that creates your design

This setup creates a perfect feedback loop: adjust a property, see what changes visually, and understand the underlying code simultaneously.

Creating the Explorer: How it Works

Let's look at how this tool is constructed:

HTML Structure

The application uses a clean, semantic HTML structure that separates the interface into logical sections:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS Flexbox Playground</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="header">
        <h1>CSS Flexbox Playground</h1>
        <div class="header-buttons">
            <button id="darkModeBtn">Dark Mode</button>
            <button id="copyHtmlBtn">Copy HTML</button>
            <button id="copyCssBtn">Copy CSS</button>
            <button id="addItemBtn">Add Item</button>
        </div>
    </div>
    <div class="container">
        <div class="sidebar">
            <div class="controls">
                <h3>Container Properties</h3>
                <div class="control-group">
                    <label for="display">Display</label>
                    <select id="display">
                        <option value="flex">flex</option>
                        <option value="inline-flex">inline-flex</option>
                    </select>
                </div>
                <div class="control-group">
                    <label for="flexDirection">Flex Direction</label>
                    <select id="flexDirection">
                        <option value="row">row</option>
                        <option value="row-reverse">row-reverse</option>
                        <option value="column">column</option>
                        <option value="column-reverse">column-reverse</option>
                    </select>
                </div>
                <div class="control-group">
                    <label for="justifyContent">Justify Content</label>
                    <select id="justifyContent">
                        <option value="flex-start">flex-start</option>
                        <option value="flex-end">flex-end</option>
                        <option value="center">center</option>
                        <option value="space-between">space-between</option>
                        <option value="space-around">space-around</option>
                        <option value="space-evenly">space-evenly</option>
                    </select>
                </div>
                <div class="control-group">
                    <label for="alignItems">Align Items</label>
                    <select id="alignItems">
                        <option value="stretch">stretch</option>
                        <option value="flex-start">flex-start</option>
                        <option value="flex-end">flex-end</option>
                        <option value="center">center</option>
                        <option value="baseline">baseline</option>
                    </select>
                </div>
                <div class="control-group">
                    <label for="flexWrap">Flex Wrap</label>
                    <select id="flexWrap">
                        <option value="nowrap">nowrap</option>
                        <option value="wrap">wrap</option>
                        <option value="wrap-reverse">wrap-reverse</option>
                    </select>
                </div>
                <h3>Item Properties</h3>
                <div class="control-group">
                    <label for="itemSelector">Select Item</label>
                    <select id="itemSelector"></select>
                </div>
                <div class="control-group">
                    <label for="itemBackground">Background Color</label>
                    <input type="color" id="itemBackground" value="#007bff">
                </div>
                <div class="control-group">
                    <label for="itemText">Text Content</label>
                    <input type="text" id="itemText" value="Item">
                </div>
            </div>
        </div>
        <div class="playground">
            <div class="flex-container" id="flexContainer">
                <div class="flex-item" draggable="true">
                    1
                    <span class="remove-icon">โœ•</span>
                </div>
                <div class="flex-item" draggable="true">
                    2
                    <span class="remove-icon">โœ•</span>
                </div>
                <div class="flex-item" draggable="true">
                    3
                    <span class="remove-icon">โœ•</span>
                </div>
            </div>
        </div>
        <div class="code-panel">
            <div class="code-section">
                <h3>HTML</h3>
                <pre id="htmlCode"></pre>
            </div>
            <div class="code-section">
                <h3>CSS</h3>
                <pre id="cssCode"></pre>
            </div>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Core Styling

The CSS not only styles our interface but also implements the Flexbox behaviors we're experimenting with:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Segoe UI', sans-serif;
}

body {
    background: #f0f2f5;
    color: #333;
    line-height: 1.6;
    height: 100vh;
    display: flex;
    flex-direction: column;
    transition: all 0.3s ease;
}

body.dark-mode {
    background: #1a1a1a;
    color: #fff;
}

.container {
    display: flex;
    flex: 1;
    overflow: hidden;
}

.header {
    padding: 15px 20px;
    background: #007bff;
    color: white;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.header-buttons button {
    padding: 8px 15px;
    margin-left: 10px;
    border: none;
    border-radius: 5px;
    background: #fff;
    color: #007bff;
    cursor: pointer;
    transition: all 0.3s;
}

.header-buttons button:hover {
    background: #e9ecef;
}

.sidebar {
    width: 300px;
    background: #fff;
    padding: 20px;
    box-shadow: 2px 0 5px rgba(0,0,0,0.1);
    overflow-y: auto;
}

body.dark-mode .sidebar {
    background: #2d2d2d;
}

.playground {
    flex: 1;
    padding: 20px;
    background: #fff;
    margin: 20px;
    border-radius: 10px;
    box-shadow: 0 0 15px rgba(0,0,0,0.1);
}

body.dark-mode .playground {
    background: #2d2d2d;
}

.controls h3 {
    margin-bottom: 15px;
    color: #007bff;
}

.control-group {
    margin-bottom: 15px;
}

label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
    color: #555;
}

body.dark-mode label {
    color: #ddd;
}

select, input[type="text"], input[type="color"] {
    width: 100%;
    padding: 8px;
    border: 1px solid #ddd;
    border-radius: 5px;
    background: #f9f9f9;
    cursor: pointer;
}

input[type="color"] {
    height: 40px;
    padding: 0;
}

body.dark-mode select,
body.dark-mode input[type="text"],
body.dark-mode input[type="color"] {
    background: #3a3a3a;
    color: #fff;
    border-color: #555;
}

.flex-container {
    min-height: 200px;
    border: 2px dashed #666;
    border-radius: 5px;
    padding: 10px;
    background: #f8f9fa;
    transition: all 0.3s ease;
}

body.dark-mode .flex-container {
    background: #333;
    border-color: #888;
}

.flex-item {
    padding: 20px;
    margin: 5px;
    border-radius: 5px;
    text-align: center;
    cursor: move;
    transition: all 0.2s ease;
    user-select: none;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
}

.flex-item:hover {
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}

.flex-item .remove-icon {
    display: none;
    position: absolute;
    top: 5px;
    right: 5px;
    width: 16px;
    height: 16px;
    background: rgba(255, 255, 255, 0.8);
    color: #333;
    border-radius: 50%;
    text-align: center;
    line-height: 16px;
    font-size: 12px;
    cursor: pointer;
    transition: all 0.2s ease;
}

.flex-item:hover .remove-icon {
    display: block;
}

.flex-item .remove-icon:hover {
    background: #ff4444;
    color: white;
}

.flex-item.dragging .remove-icon {
    display: none;
}

.flex-item.dragging {
    opacity: 0.5;
}

.code-panel {
    width: 300px;
    background: #1a1a1a;
    color: #fff;
    padding: 20px;
    overflow-y: auto;
    font-family: 'Courier New', monospace;
    font-size: 14px;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.code-section h3 {
    margin-bottom: 10px;
    color: #007bff;
}
Enter fullscreen mode Exit fullscreen mode

Interactive Functionality

JavaScript brings everything to life, handling property changes, drag-and-drop functionality, and code generation:

document.addEventListener('DOMContentLoaded', () => {
    const flexContainer = document.getElementById('flexContainer');
    const htmlCode = document.getElementById('htmlCode');
    const cssCode = document.getElementById('cssCode');
    const controls = document.querySelectorAll('.controls select');
    const darkModeBtn = document.getElementById('darkModeBtn');
    const copyHtmlBtn = document.getElementById('copyHtmlBtn');
    const copyCssBtn = document.getElementById('copyCssBtn');
    const addItemBtn = document.getElementById('addItemBtn');
    const itemSelector = document.getElementById('itemSelector');
    const itemBackground = document.getElementById('itemBackground');
    const itemText = document.getElementById('itemText');
    let itemCount = 3;

    // Populate item selector
    function updateItemSelector() {
        itemSelector.innerHTML = '';
        const items = flexContainer.querySelectorAll('.flex-item');
        items.forEach((item, index) => {
            const option = document.createElement('option');
            option.value = index;
            option.textContent = `Item ${index + 1}`;
            itemSelector.appendChild(option);
        });
        // Ensure controls reflect the currently selected item
        updateItemControls();
    }

    // Update item controls based on selected item
    function updateItemControls() {
        const selectedIndex = parseInt(itemSelector.value, 10); // Ensure it's an integer
        const selectedItem = flexContainer.children[selectedIndex];
        if (selectedItem) {
            itemBackground.value = rgbToHex(selectedItem.style.backgroundColor) || '#007bff';
            itemText.value = selectedItem.firstChild.textContent || 'Item';
            updateTextColor(selectedItem);
        }
    }

    // Convert RGB to Hex
    function rgbToHex(rgb) {
        if (!rgb || rgb === '') return '#007bff';
        const match = rgb.match(/\d+/g);
        if (!match) return '#007bff';
        const [r, g, b] = match.map(Number);
        return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
    }

    // Calculate luminance and set text color
    function updateTextColor(item) {
        const bgColor = item.style.backgroundColor || '#007bff';
        const match = bgColor.match(/\d+/g);
        if (!match) return; // If no valid color, skip
        const [r, g, b] = match.map(Number);
        const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
        item.style.color = luminance > 0.5 ? '#000000' : '#FFFFFF';
    }

    // Update Flexbox properties and both code displays
    function updateFlexbox() {
        const styles = {
            display: document.getElementById('display').value,
            flexDirection: document.getElementById('flexDirection').value,
            justifyContent: document.getElementById('justifyContent').value,
            alignItems: document.getElementById('alignItems').value,
            flexWrap: document.getElementById('flexWrap').value
        };

        Object.assign(flexContainer.style, styles);
        updateHTMLCode();
        updateCSSCode(styles);
    }

    // Update HTML code display
    function updateHTMLCode() {
        const itemsHTML = Array.from(flexContainer.children)
            .map(item => `    <div class="flex-item" style="background-color: ${item.style.backgroundColor || '#007bff'}; color: ${item.style.color}">${item.firstChild.textContent}</div>`)
            .join('\n');
        htmlCode.textContent = `<div class="flex-container">\n${itemsHTML}\n</div>`;
    }

    // Update CSS code display
    function updateCSSCode(styles) {
        cssCode.textContent = `.flex-container {
    display: ${styles.display};
    flex-direction: ${styles.flexDirection};
    justify-content: ${styles.justifyContent};
    align-items: ${styles.alignItems};
    flex-wrap: ${styles.flexWrap};
}

.flex-item {
    padding: 20px;
    margin: 5px;
    border-radius: 5px;
    text-align: center;
}`;
    }

    // Update selected item's properties
    function updateItemProperties() {
        const selectedIndex = parseInt(itemSelector.value, 10); // Ensure integer
        const selectedItem = flexContainer.children[selectedIndex];
        if (selectedItem) {
            selectedItem.style.backgroundColor = itemBackground.value;
            selectedItem.firstChild.textContent = itemText.value;
            updateTextColor(selectedItem);
            updateHTMLCode();
        }
    }

    // Drag and Drop functionality
    flexContainer.addEventListener('dragstart', (e) => {
        if (e.target.classList.contains('flex-item')) {
            e.target.classList.add('dragging');
        }
    });

    flexContainer.addEventListener('dragend', (e) => {
        if (e.target.classList.contains('flex-item')) {
            e.target.classList.remove('dragging');
        }
    });

    flexContainer.addEventListener('dragover', (e) => {
        e.preventDefault();
    });

    flexContainer.addEventListener('drop', (e) => {
        e.preventDefault();
        const dragging = document.querySelector('.dragging');
        const afterElement = getDragAfterElement(flexContainer, e.clientX, e.clientY);
        if (afterElement == null) {
            flexContainer.appendChild(dragging);
        } else {
            flexContainer.insertBefore(dragging, afterElement);
        }
        updateHTMLCode();
        updateItemSelector();
    });

    function getDragAfterElement(container, x, y) {
        const draggableElements = [...container.querySelectorAll('.flex-item:not(.dragging)')];
        return draggableElements.reduce((closest, child) => {
            const box = child.getBoundingClientRect();
            const offsetX = x - (box.left + box.width / 2);
            const offsetY = y - (box.top + box.height / 2);
            if (offsetX < 0 && offsetX > closest.offset) {
                return { offset: offsetX, element: child };
            }
            return closest;
        }, { offset: Number.NEGATIVE_INFINITY }).element;
    }

    // Remove Item functionality
    flexContainer.addEventListener('click', (e) => {
        if (e.target.classList.contains('remove-icon')) {
            const item = e.target.parentElement;
            if (flexContainer.children.length > 1) {
                item.remove();
                updateItemNumbers();
                updateHTMLCode();
                updateItemSelector();
            } else {
                alert('Cannot remove the last item!');
            }
        }
    });

    // Update item numbers after removal
    function updateItemNumbers() {
        const items = flexContainer.querySelectorAll('.flex-item');
        items.forEach((item, index) => {
            item.firstChild.textContent = item.firstChild.textContent.match(/\d+/) ? index + 1 : item.firstChild.textContent;
        });
        itemCount = items.length;
    }

    // Dark Mode Toggle
    darkModeBtn.addEventListener('click', () => {
        document.body.classList.toggle('dark-mode');
        darkModeBtn.textContent = document.body.classList.contains('dark-mode') 
            ? 'Light Mode' 
            : 'Dark Mode';
    });

    // Copy HTML
    copyHtmlBtn.addEventListener('click', () => {
        const code = htmlCode.textContent;
        navigator.clipboard.writeText(code).then(() => {
            alert('HTML code copied to clipboard!');
        });
    });

    // Copy CSS
    copyCssBtn.addEventListener('click', () => {
        const code = cssCode.textContent;
        navigator.clipboard.writeText(code).then(() => {
            alert('CSS code copied to clipboard!');
        });
    });

    // Add New Item
    addItemBtn.addEventListener('click', () => {
        itemCount++;
        const newItem = document.createElement('div');
        newItem.className = 'flex-item';
        newItem.draggable = true;
        newItem.style.backgroundColor = itemBackground.value;
        newItem.innerHTML = `${itemText.value}<span class="remove-icon">โœ•</span>`;
        flexContainer.appendChild(newItem);
        updateTextColor(newItem);
        updateHTMLCode();
        updateItemSelector();
    });

    // Event Listeners for controls
    controls.forEach(control => {
        control.addEventListener('change', updateFlexbox);
    });

    itemSelector.addEventListener('change', updateItemControls);
    itemBackground.addEventListener('input', updateItemProperties);
    itemText.addEventListener('input', updateItemProperties);

    // Initial setup
    updateFlexbox();
    flexContainer.querySelectorAll('.flex-item').forEach(item => {
        item.style.backgroundColor = '#007bff'; // Set initial background
        updateTextColor(item);
    });
    updateItemSelector();
});
Enter fullscreen mode Exit fullscreen mode

Flexbox Container Properties Explained

The left panel provides controls for all major Flexbox container properties:

Display Type

Choose between:

  • flex - Creates a block-level flex container
  • inline-flex - Creates an inline-level flex container

Main Axis Direction

Set your layout flow with:

  • row - Items arranged horizontally (default)
  • row-reverse - Items arranged horizontally in reverse order
  • column - Items stacked vertically
  • column-reverse - Items stacked vertically in reverse order

This choice fundamentally affects how other properties work! ๐Ÿงญ

Main Axis Alignment (justify-content)

Position items along the main axis:

  • flex-start - Items packed toward start
  • flex-end - Items packed toward end
  • center - Items centered along main axis
  • space-between - Items evenly distributed with first at start, last at end
  • space-around - Items with equal space around them
  • space-evenly - Items with equal space between them

Cross Axis Alignment (align-items)

Control positioning perpendicular to the main axis:

  • stretch - Items expand to fill container (default)
  • flex-start - Items aligned at cross-axis start
  • flex-end - Items aligned at cross-axis end
  • center - Items centered on cross axis
  • baseline - Items aligned by text baselines

Overflow Behavior (flex-wrap)

Control what happens when items would overflow:

  • nowrap - All items forced to single line (default)
  • wrap - Items wrap to additional lines as needed
  • wrap-reverse - Items wrap to additional lines in reverse direction

Item-Level Customization

Beyond container properties, you can also:

  • Select specific flex items to customize
  • Change background colors with a visual picker
  • Modify the text content of any item

The tool even automatically adjusts text color based on background brightness for optimal readability! ๐ŸŽจ

Beyond Basic Controls

What sets this explorer apart are its interactive features:

Intuitive Drag and Drop

Click and drag items to reposition them within the container. This helps you:

  • Experience how Flexbox ordering works
  • Test different arrangements without writing code
  • Understand spatial relationships between items

Dynamic Item Management

  • Add new items with the "Add Item" button
  • Remove unwanted items by hovering and clicking the "โœ•" icon

These features let you see how Flexbox adapts to different numbers of items.

Light/Dark Mode

Switch between color themes with a single click - perfect for any working environment or time of day. ๐ŸŒ“

Code Export

Once you've created the perfect layout:

  • Copy HTML with one click
  • Copy CSS with one click

Instantly implement your experiments in real projects!

Layouts to Try

Here are some common patterns to experiment with:

Perfect Centering

The infamous centering problem, solved with just two properties:

  • justify-content: center
  • align-items: center

Try it and watch your content snap to the center of the container! ๐ŸŽฏ

Responsive Navigation Bar

Create a flexible navigation menu:

  • display: flex
  • justify-content: space-between (or space-around)

See how items distribute evenly, maintaining spacing as the window resizes.

Card Grid with Wrapping

Build a responsive grid of cards:

  • Set flex-wrap: wrap
  • Add several items
  • Try different justify-content values

Watch how items maintain consistent spacing while wrapping to new rows as needed.

Real-World Applications

Through exploration, you'll discover Flexbox excels at:

  • Header layouts with logos and navigation
  • Photo galleries with consistent spacing
  • Social media feeds with profile images and content
  • Form elements with aligned labels and inputs
  • Feature comparison tables and pricing panels

Learning Through Doing

This Flexbox Explorer transforms abstract CSS concepts into tangible, visible experiences. Instead of memorizing properties, you'll develop an intuition through direct manipulation and instant feedback.

I encourage you to:

  • Test extreme values to see how layouts respond
  • Create components you frequently need in projects
  • Experiment with different numbers of items
  • Try recreating layouts from your favorite websites

With each experiment, Flexbox becomes less mysterious and more intuitive โ€” turning what once seemed like CSS sorcery into just another tool in your kit. โœจ

What Flexbox layouts have you struggled with? Try building them in the explorer and share your results in the comments!

Heroku

Deliver your unique apps, your own way.

Heroku tackles the toil โ€” patching and upgrading, 24/7 ops and security, build systems, failovers, and more. Stay focused on building great data-driven applications.

Learn More

Top comments (0)

Sentry image

See why 4M developers consider Sentry, โ€œnot bad.โ€

Fixing code doesnโ€™t have to be the worst part of your day. Learn how Sentry can help.

Learn more

๐Ÿ‘‹ Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someoneโ€™s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay