I've been watching the discourse around vibe coding explode over the past few months. Developers (and non developers alike!) love the speed. Ship fast, iterate later. Prompt your way to a working prototype in minutes. After trying this myself for several personal projects, I get it!
But... I've also been cleaning up the aftermath. Code that works on demo day or for a basic prototype but falls to pieces the moment you try to extend it. Components that technically render but fight you on every customization. I learned the hard way that speed without structure is just borrowing time from your future self.
I started thinking of it like the difference between sketching on a napkin and working from blueprints. Both get you a picture of a house, but only one of them survives reviews with a building inspector.
In this post I'll walk you through what happens when you build the same feature two different ways: pure vibe coding (prompt, generate, ship) versus Kiro's spec-driven workflow (requirements, design, implement by task). Same feature, same complexity, different outcomes by the end.
The Feature: A Horizontal Scroll Carousel for a Portfolio Site
I have a personal portfolio site (lausalin.dev) that displays my blog posts and videos as large card-style placeholders in a grid layout. Each card takes up significant vertical space, which means visitors have to scroll through a wall of content to see everything. The site used to show 6+ blog cards and 6+ video cards stacked vertically.
The goal: replace those stacked grids with horizontal scroll carousels. Compact card elements that users can scroll through left-to-right, showing 3-4 items at a time with smooth navigation. The vision is Netflix-style content rows instead of a Pinterest-style wall.
The acceptance criteria are identical for both approaches:
- Horizontal scrollable container showing 3-4 cards at a time on desktop
- Responsive behavior (2 cards on tablet, 1 on mobile with swipe)
- Smooth scroll-snap alignment so cards land cleanly
- Navigation arrows for non-touch devices
- Keyboard accessibility (arrow key navigation within the carousel)
- Maintain the existing card content (title, date, read time, thumbnail)
- Lazy loading for off-screen card images
This is a UI feature that looks simple on the surface but has real depth once you consider responsiveness, accessibility, and scroll behavior quirks.
Path 1: Vibe Coding It
I opened a chat-based vibe session with Kiro and typed:
"Replace the blog-grid and video grid sections on my portfolio site with horizontal scroll carousels. Show 3-4 cards at a time, add navigation arrows. Site should be responsive both on web and mobile."
In about 3 minutes I had a working local render. Horizontal scroll container, CSS scroll-snap, arrow buttons, the whole thing.
Ship it, right?
Several problems hid in that rough draft:
No scroll-snap edge cases handled. The CSS scroll-snap-type: x mandatory worked for the default viewport width, but at certain breakpoints the snap points misaligned with the card widths. Cards would land half-visible with no way to reach the snapped position.
Accessibility was an afterthought. The arrow buttons had no aria-label. The carousel had no role="region" or aria-roledescription. Keyboard users couldn't navigate between cards. Screen readers announced nothing useful.
Touch and mouse scroll conflicted. On trackpad, horizontal scroll worked. On touch devices the swipe gesture competed with the page's vertical scroll, creating a janky diagonal movement.
No loading strategy. All card images (including ones five scroll-lengths offscreen) loaded on page init. On mobile connections this meant 2+ seconds of layout shift as images popped in.
It worked fine enough for the ~5 minutes of effort. But every device I tested afterward revealed a new edge case that would've required going back into the prompting and iterating repeatedly (AKA: more token consumption and additional time waste)
Here's a screenshot of what we had from the vibe coded local render:
Path 2: Kiro's Spec-Driven Workflow
Same starting intent. I opened Kiro, toggle on "Spec Mode" and described the feature: "Replace the blog-grid and video grid sections on my portfolio site with horizontal scroll carousels." But instead of jumping straight to code, this time Kiro generated a requirements document following the requirements-first workflow.
Step 1: Requirements Spec
Kiro produced requirements using EARS notation:
I reviewed these before any code existed and caught a gap I hadn't considered: my original prompt said nothing about what happens at the ends of the carousel. Should the arrows disable? Should the scroll wrap? The local vibe coded render hadn't accounted for the end of the carousel. As it stood, nothing happened when a user clicked past the last loaded media.
I added a requirement for that behavior. In vibe coding, I didn't (and wouldn't) have noticed this behavior until a user clicked the right arrow on the last card and nothing happened.
Step 2: Technical Design
Kiro also produced a design doc with the following:
Key design decisions:
- Uses native CSS scroll-snap for touch/swipe settling rather than custom JS animation
- A single shared carousel.js module works across all three card types (blog, video, events)
- Pure computational core (CarouselMetrics) is separated from the DOM controller so it can be property-tested without a browser
Architecture (3 layers):
- Renderers: existing functions that build card markup (unchanged responsibilities)
- DOM controller (initCarousel): builds nav buttons, measures layout, binds events, manages ARIA
- CarouselMetrics: pure functions for card sizing, step distance, edge detection, snap targets
Responsive behavior:
- Desktop (>640px): 2+ whole cards visible
- Mobile (≤640px): 1 full card + peek of next (10-40%)
- Very small (<240px): single full-width card
Accessibility: ARIA region/roledescription, live announcements ("Showing X–Y of N"), keyboard operable (Enter/Space on arrows), focus management with scrollIntoView, reduced-motion support via prefers-reduced-motion
Error handling: fetch timeouts (10s), graceful empty states, malformed data resilience, non-interactive cards when URL is absent
Step 3: Implementation Tasks
The final phase of the spec-driven approach is the breaking down of the design doc into discrete, ordered tasks.
Each task maps back to specific requirements. When I accept them, Kiro implements them one at a time (or all at once if I prefer), verifying each against the spec before moving to the next.
The Result
Major changes I noted from vibe to spec for this site:
-
Responsive by design, not by patch. CSS custom properties (
--card-width,--cards-visible,--card-gap) recalculate per breakpoint. One source of truth instead of scattered magic numbers. -
Accessibility built in from task 5.
role="region",aria-roledescription="carousel",aria-labelon each card, roving tabindex, arrow key handlers that sync scroll position with focus. -
Touch scroll isolation.
overscroll-behavior-x: containon the container, combined with a threshold-based gesture detector that only activates horizontal scroll after 15px of horizontal movement (preventing diagonal jank). - Lazy loading with Intersection Observer. Cards get placeholder dimensions immediately (no layout shift), images load when within one viewport-width of visible area.
- Arrow state management. A scroll event listener calculates position and disables arrows at boundaries. Debounced to avoid performance hits during momentum scrolling.
Did it take longer? Yes. Roughly 30 minutes from prompt to full spec versus 5min from vibe to local render. But the difference in catching the scroll-end behavior gap, the touch gesture conflict, and the accessibility requirements before they became bugs discovered by users was worth it for me.
The Side-by-Side
| Dimension | Vibe Coding | Kiro Spec-Driven |
|---|---|---|
| Time to first render | ⚡ ~5 minutes | ⏱️ ~30 minutes |
| Works on desktop Chrome | ✅ Yes | ✅ Yes |
| Works on mobile Safari | ⚠️ Diagonal scroll janky | ✅ Smooth |
| Keyboard navigable | ❌ No | ✅ Full arrow key support |
| Screen reader usable | ❌ Silent | ✅ Announces card content |
| Handles viewport resize | ⚠️ Breaks at 1000px | ✅ Recalculates cleanly |
The first-render speed difference isn't marginal, but having to fix all the issues that came up after would've taken longer depending on when they were discovered (if at all). The gap shows up quickly the moment you test beyond the happy path with a different browser, a different screen size, a keyboard user, a slow connection, etc.
When Vibe Coding Is the Right Call
One thing to get clear: I'm not here to say vibe coding is always wrong. It's a still a valid approach for many scenarios that I've found myself in such as:
- Prototypes which you'll delete after validating an idea
- Hackathon projects where shipping fast IS the actual goal
- Exploratory spikes to test if an approach even works before investing in it
- Scripts you'll run once and never look at again
- Learning a new framework where the code itself is disposable
The moment you plan to deploy something users will actually interact with, especially across different devices, with different abilities, and on different connections, that's when I've found the time invested in specs earns its keep.
In this relatively simple UI improvement for my website, if I just wanted to confirm that CSS scroll-snap could handle the layout before committing, vibe coding is perfect for that test. But the version going on my live site? That needs the spec to make sure the edge cases are covered.
Final Takeaway
Vibe coding treats code as the primary artifact. You prompt, you get code, done.
Kiro treats the spec as the primary artifact. Code is a build output from that spec and this can be a meaningful shift for some folks. It means:
- Requirements are explicit and reviewable, not trapped in chat history or your head
- Edge cases surface during spec review, not after deployment when a user on an iPad reports the bug
- Changes start at the spec level, not at the code level. When I later want to add an "Events" carousel, I update the spec and Kiro generates a new component that inherits all the accessibility and responsive work from the original
For anyone who has shipped a "simple" UI component only to spend the next week fixing device-specific edge cases, this front-loaded approach pays for itself fast. Especially when you get down to the bottom of how much each roundtrip conversation with the agent can cost if you spend the entire time vibe coding.
The Kiro free tier gives you 50 credits per month with no credit card. That's enough to run this experiment on a real feature. Download Kiro here and check out the difference for yourself.



Top comments (0)