Hero with Live Slug Generator — Free HTML CSS JS Snippet
Hero with Live Slug Generator Demo · Heroes · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<section class="ldi-hero">
<span class="ldi-eyebrow">See it work before you sign up</span>
<h1 class="ldi-h1">Clean URLs,<br>generated as you type</h1>
<p class="ldi-sub">Type a post title below and watch the SEO-friendly slug build itself in real time — the exact logic that runs on every post you publish.</p>
<div class="ldi-demo">
<label class="ldi-label" for="ldiInput">Post title</label>
<input type="text" id="ldiInput" class="ldi-input" placeholder="e.g. 10 Tips for a Faster Morning Routine" maxlength="90" autocomplete="off">
<div class="ldi-out">
<span class="ldi-out-label">webdevpuneet.com/blog/</span>
<span class="ldi-out-slug" id="ldiSlug">your-post-title</span>
</div>
<div class="ldi-meta">
<span id="ldiCount">0 / 90 characters</span>
<button type="button" class="ldi-copy" id="ldiCopy">Copy slug</button>
</div>
</div>
<a href="#" class="ldi-cta">Start writing for free</a>
</section>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0d1117;color:#e6edf3}
.ldi-hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:14px;padding:24px}
.ldi-eyebrow{font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#79c0ff}
.ldi-h1{font-size:clamp(30px,5vw,50px);font-weight:800;line-height:1.14;letter-spacing:-.02em}
.ldi-sub{font-size:15px;color:#8b949e;line-height:1.7;max-width:480px;margin-bottom:10px}
.ldi-demo{width:100%;max-width:480px;background:#161b22;border:1px solid #30363d;border-radius:16px;padding:22px;text-align:left;box-shadow:0 24px 60px rgba(0,0,0,.4)}
.ldi-label{display:block;font-size:11.5px;font-weight:700;color:#8b949e;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px}
.ldi-input{width:100%;background:#0d1117;border:1px solid #30363d;border-radius:9px;padding:11px 13px;color:#e6edf3;font-size:14.5px;font-family:inherit;outline:none;transition:border-color .15s}
.ldi-input:focus{border-color:#58a6ff}
.ldi-out{display:flex;align-items:baseline;gap:2px;margin-top:16px;padding:12px 13px;background:#0d1117;border:1px dashed #30363d;border-radius:9px;font-size:13.5px;font-family:'SFMono-Regular',Consolas,monospace;overflow-x:auto;white-space:nowrap}
.ldi-out-label{color:#6e7681}
.ldi-out-slug{color:#7ee787;font-weight:700}
.ldi-meta{display:flex;align-items:center;justify-content:space-between;margin-top:12px}
.ldi-meta span{font-size:12px;color:#6e7681}
.ldi-copy{background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:7px;padding:6px 13px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;transition:background .15s}
.ldi-copy:hover{background:#30363d}
.ldi-copy.copied{background:#238636;border-color:#2ea043;color:#fff}
.ldi-cta{margin-top:22px;background:#238636;color:#fff;font-weight:700;font-size:15px;padding:12px 28px;border-radius:9px;text-decoration:none;box-shadow:0 8px 24px rgba(35,134,54,.32);transition:transform .15s}
.ldi-cta:hover{transform:translateY(-2px)}// A real, deterministic slug transform — not a canned animation. Typing runs the exact
// slugify logic a CMS would run on save: lowercase, strip diacritics/punctuation, collapse
// whitespace and repeated hyphens, trim to a max length.
var input = document.getElementById('ldiInput');
var slugEl = document.getElementById('ldiSlug');
var countEl = document.getElementById('ldiCount');
var copyBtn = document.getElementById('ldiCopy');
var MAX_LEN = 90;
function slugify(text) {
return text
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.trim()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
}
function update() {
var raw = input.value;
countEl.textContent = raw.length + ' / ' + MAX_LEN + ' characters';
var slug = slugify(raw);
slugEl.textContent = slug.length ? slug : 'your-post-title';
}
input.addEventListener('input', update);
copyBtn.addEventListener('click', function () {
var text = slugEl.textContent;
if (!text) return;
var finish = function () {
copyBtn.textContent = 'Copied!';
copyBtn.classList.add('copied');
window.setTimeout(function () {
copyBtn.textContent = 'Copy slug';
copyBtn.classList.remove('copied');
}, 1400);
};
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(finish).catch(finish);
} else {
finish();
}
});
update();Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Hero with Live Slug Generator Demo — A Hero You Can Actually Use

Most heroes ask a visitor to take the product's usefulness on faith — a headline claims speed or simplicity, and the visitor has to sign up to find out if that's true. This hero instead embeds a small, fully working piece of the product directly in the fold: type a blog post title, and a real slugify function runs on every keystroke to build the exact URL slug that would be saved. There is no fake typing animation and no canned example rotating on a timer — the output is a genuine, deterministic transform of whatever the visitor types.
The slugify function, explained
slugify(text) runs six real transformation steps in sequence: normalize('NFD') decomposes accented characters into a base letter plus a combining diacritical mark, and the following replace(/[\u0300-\u036f]/g, '') strips those diacritical marks — so "Café" becomes "cafe" rather than being dropped or mangled. toLowerCase() and trim() normalize case and outer whitespace. replace(/[^a-z0-9\s-]/g, '') removes anything that isn't a lowercase letter, digit, whitespace, or hyphen — punctuation, emoji, and symbols all disappear. replace(/\s+/g, '-') turns any run of whitespace into a single hyphen, and replace(/-+/g, '-') collapses any resulting run of multiple hyphens (which happens when the title itself contained a hyphen next to a space) into one. A final replace(/^-|-$/g, '') trims a leading or trailing hyphen that can appear if the title started or ended with punctuation.
Why this exact sequence, not a shortcut regex
A single regex like replace(/[^a-z0-9]+/g, '-') looks tempting but silently breaks on accented input — "Café" would produce "caf-" (the é gets stripped as a non-ASCII character, leaving a dangling hyphen) instead of the correct "cafe". Running normalize('NFD') and stripping combining marks *before* the character-class filter is what makes accented titles produce clean, readable slugs instead of a mangled one.
Live output, not deferred to submit
The input event fires update() on every keystroke, which recomputes the slug and rewrites both the character counter and the slug display immediately. There is no debounce — slugify() is cheap enough (a handful of regex passes over a short string) to run on every keystroke without any perceptible lag, so the visitor sees the transform happen character-by-character as they type, which is the entire point of the demo.
The copy-to-clipboard button
Clicking "Copy slug" calls navigator.clipboard.writeText() when available, with a fallback that still shows the "Copied!" confirmation state even in environments where the Clipboard API is unavailable or blocked (some sandboxed iframes, for instance) — the button never silently does nothing. The confirmation state reverts after 1.4 seconds via setTimeout.
Why an empty input still shows a value
When raw is empty, slugify('') returns an empty string, and rather than showing a blank output box (which looks broken), update() falls back to displaying the placeholder text your-post-title so the demo always shows *something* meaningful, guiding the visitor toward typing.
Adapting this pattern to a different product
The structure — an input, a live-updating output panel styled to look like a real result, a character counter, and a copy action — generalizes well beyond slugs: a password-strength meter, a Markdown-to-HTML live preview, a regex tester, or a currency formatter could all sit in the same hero shape. Swap slugify() for whatever transform your product actually performs, and the rest of the layout, event wiring, and copy-button logic carries over unchanged.
Step by step
How to Use
- 1Type into the inputThe slug output beneath updates on every keystroke using a real slugify function — no canned demo text.
- 2Try accented or punctuated inputType something like "Café: A Cozy Guide!" to see diacritics stripped and punctuation removed correctly.
- 3Click "Copy slug"Copies the current generated slug to the clipboard and shows a temporary confirmation state.
- 4Adapt slugify() to your productReplace the transform function in the JS panel with your own logic (password strength, Markdown preview, etc.) — the input/output wiring stays the same.
- 5Change the max lengthEdit the MAX_LEN constant and the input's maxlength attribute together.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
It is a real, deterministic JavaScript function. Every keystroke fires the input event, which calls slugify() on the current input value and writes the result to the output element — there is no pre-recorded sequence or timer-driven fake typing.
Unicode NFD (Normalization Form Decomposed) splits an accented character like \u00e9 into a base letter e plus a separate combining accent mark. Stripping combining marks (the \u0300-\u036f range) after this decomposition turns "Café" into "cafe" correctly. Filtering with a plain [^a-z0-9] regex on the original, non-decomposed string would instead delete the accented character entirely, leaving "caf" with a dangling hyphen.
slugify() only runs a handful of regex passes over a short string (a blog title is rarely more than 90 characters), which completes in a fraction of a millisecond — far below any perceptible lag threshold. Debouncing would only add a false delay to a demo whose entire purpose is showing the transform happen live.
The copy handler checks for navigator.clipboard.writeText before calling it. If it is unavailable or the promise rejects (common in some sandboxed iframe contexts), the code still calls the same finish() function that shows the "Copied!" confirmation state, so the button never appears to silently fail even where the actual clipboard write could not occur.
Replace the slugify() function body with whatever transform your product performs, and update the output panel labels and placeholder text to match. The input listener, character counter, and copy-to-clipboard wiring do not need to change — they operate on whatever string the transform function returns.
An empty output box next to an empty input looks like the demo is broken rather than simply unused. update() checks if the computed slug has zero length and falls back to a placeholder string, so the panel always shows a plausible example, nudging the visitor to start typing.