HTML Best Practices
Solid HTML is the foundation of every great website. These tips help you write semantic, robust, and future-proof markup that browsers and screen readers love.
Setting explicit width and height attributes on images lets the browser reserve the correct space before the image loads, eliminating Cumulative Layout Shift (CLS) โ one of Google's Core Web Vitals.
<!-- โ Bad: browser doesn't know size until image loads --> <img src="hero.jpg" alt="Hero image"> <!-- โ Good: space reserved immediately --> <img src="hero.jpg" alt="Hero image" width="1200" height="630" loading="lazy">
aspect-ratio: auto in CSS and the browser will calculate the correct ratio automatically, even when CSS resizes the image.A document outline built with properly nested headings helps both search engines understand your content hierarchy and screen reader users navigate the page efficiently.
<h1>Page Title (one per page)</h1> <h2>Main Section</h2> <h3>Sub-section</h3> <h3>Another sub-section</h3> <h2>Another Main Section</h2>
When opening links in a new tab with target="_blank", the opened page can access your page via window.opener. Adding rel="noopener noreferrer" prevents this security vulnerability and also stops referrer info being sent.
<!-- โ Vulnerable --> <a href="https://example.com" target="_blank">Visit</a> <!-- โ Secure --> <a href="https://example.com" target="_blank" rel="noopener noreferrer">Visit</a>
The autocomplete attribute tells browsers and password managers how to pre-fill form fields. This dramatically improves mobile form completion rates.
<input type="text" autocomplete="given-name"> <input type="email" autocomplete="email"> <input type="tel" autocomplete="tel"> <input type="password" autocomplete="current-password"> <input type="text" autocomplete="postal-code">
Open Graph meta tags control how your page looks when shared on social media platforms like Facebook, LinkedIn, and Twitter/X. Without them, platforms pick content at random.
<meta property="og:title" content="Page Title"> <meta property="og:description" content="Short description"> <meta property="og:image" content="https://yourdomain.com/og.jpg"> <meta property="og:url" content="https://yourdomain.com/page"> <meta property="og:type" content="website"> <!-- Twitter/X --> <meta name="twitter:card" content="summary_large_image">
CSS Tips & Tricks
Modern CSS is incredibly powerful. These tips cover layout, custom properties, and patterns that save you time and keep your stylesheets clean.
Stop fighting with positioning hacks. CSS Grid's place-items shorthand centers content both horizontally and vertically in two lines โ no calc, no transforms.
.center-everything { display: grid; place-items: center; } /* Works for full-page centering too */ body { display: grid; place-items: center; min-height: 100vh; }
CSS custom properties (variables) let you define your design tokens once and reuse them everywhere. They also update in real-time, making dark mode and theming trivial.
:root { --color-primary: #6366f1; --color-bg: #ffffff; --radius-md: 12px; --spacing-md: 16px; } @media (prefers-color-scheme: dark) { :root { --color-bg: #0d1117; } } .btn { background: var(--color-primary); border-radius: var(--radius-md); padding: var(--spacing-md); }
clamp(min, preferred, max) allows font sizes to scale smoothly with the viewport width โ no media queries needed. The middle value is usually a vw unit.
/* clamp(minimum, preferred, maximum) */ h1 { font-size: clamp(1.8rem, 5vw, 3.5rem); } p { font-size: clamp(0.9rem, 2vw, 1.1rem); line-height: 1.7; max-width: 65ch; /* optimal reading width */ }
Enable smooth scrolling globally and use scroll-margin-top on anchor targets to ensure sticky headers don't overlap the content being scrolled to.
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } /* Offset for sticky header (e.g. 70px tall) */ [id] { scroll-margin-top: 90px; }
scroll-behavior: smooth in a prefers-reduced-motion: no-preference media query โ some users experience motion sickness from animated scrolling.The auto-fill + minmax() pattern creates a grid that automatically adjusts the number of columns based on available space โ no media queries needed.
.card-grid { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: 24px; } /* 1 col on mobile โ 2 โ 3 โ 4 automatically */
Never do outline: none globally โ it breaks keyboard navigation. Use :focus-visible to show focus rings only for keyboard users, keeping the design clean for mouse users.
/* โ Never do this */ * { outline: none; } /* โ Hide for mouse, show for keyboard */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid #6366f1; outline-offset: 3px; border-radius: 4px; }
JavaScript Tips
Write cleaner, more performant JavaScript with these modern patterns and browser APIs.
Instead of listening to the scroll event (which fires hundreds of times per second), use the IntersectionObserver API to react only when elements enter or leave the viewport.
const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); // stop watching } }); }, { rootMargin: '0px 0px -50px 0px' } ); document.querySelectorAll('.animate-on-scroll') .forEach(el => observer.observe(el));
Events like resize and input fire very rapidly. Debouncing delays execution until the user stops interacting, preventing unnecessary work.
function debounce(fn, delay = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Usage window.addEventListener('resize', debounce(() => { recalculateLayout(); }, 200) );
The modern navigator.clipboard API is promise-based and much cleaner than the old document.execCommand('copy') hack.
async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showToast('Copied!'); } catch (err) { console.error('Copy failed:', err); } } // Usage document.querySelector('.copy-btn') .addEventListener('click', () => copyToClipboard('Text to copy') );
localStorage can throw in private browsing mode or when storage is full. Always wrap it in a try/catch helper.
const storage = { get(key, fallback = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : fallback; } catch { return fallback; } }, set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } }; storage.set('theme', 'dark'); const theme = storage.get('theme', 'light');
For JavaScript-driven animations (e.g. canvas, GSAP), check the user's prefers-reduced-motion setting and simplify or disable animations accordingly.
const prefersReduced = window .matchMedia('(prefers-reduced-motion: reduce)') .matches; if (!prefersReduced) { startAnimations(); } else { showStaticFallback(); }
Performance Tips
Fast websites rank higher, convert better, and keep users happy. These tips cover the biggest wins for web performance.
Without font-display: swap, browsers hide text until the custom font loads (FOIT). With it, the system font is shown immediately, then swapped โ vastly improving Largest Contentful Paint (LCP).
<!-- Step 1: preconnect --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- Step 2: append &display=swap to the URL --> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
WebP and AVIF images are significantly smaller than JPEGs and PNGs with equal quality. Use the <picture> element to serve modern formats with a JPEG fallback for older browsers.
<picture> <source srcset="hero.avif" type="image/avif"> <source srcset="hero.webp" type="image/webp"> <img src="hero.jpg" alt="Hero image" width="1200" height="630" loading="lazy"> </picture>
Regular <script> tags block HTML parsing. defer runs the script after parsing completes (in order). async runs scripts as soon as they download (order not guaranteed).
<!-- Blocks rendering โ --> <script src="app.js"></script> <!-- Runs in order, after DOM โ (use for most scripts) --> <script src="app.js" defer></script> <!-- Runs immediately when ready โ (analytics, ads) --> <script src="analytics.js" async></script>
Stick to animating transform and opacity โ they run on the GPU compositor thread without triggering layout or paint. Add will-change only if you profile and see a real benefit.
/* โ Triggers layout (expensive) */ .bad { transition: width 0.3s, top 0.3s; } /* โ GPU-composited (cheap) */ .good { transition: transform 0.3s, opacity 0.3s; } /* Use sparingly, only before complex animations */ .modal-enter { will-change: transform; } .modal-entered { will-change: auto; } /* reset after */
Use <link rel="preload"> to tell the browser to fetch critical resources early โ before it discovers them in CSS or JS. Ideal for hero images, critical fonts, and key scripts.
<!-- Preload hero image (LCP element) --> <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> <!-- Preload critical font --> <link rel="preload" as="font" href="font.woff2" type="font/woff2" crossorigin>
Accessibility Tips
Accessibility isn't a checklist โ it's good design. These tips help make your sites usable by everyone, including screen reader users and keyboard navigators.
Buttons that contain only an icon have no visible text for screen readers to announce. Add an aria-label to provide a descriptive label.
<!-- โ Screen reader says "button" --> <button><i class="fa-solid fa-times"></i></button> <!-- โ Screen reader says "Close dialog" --> <button aria-label="Close dialog"> <i class="fa-solid fa-times" aria-hidden="true"></i> </button>
The lang attribute tells screen readers which language to use, enables correct hyphenation in CSS, and helps translation tools identify the content language. For RTL languages, also add dir="rtl".
<!-- English --> <html lang="en"> <!-- Arabic (RTL) --> <html lang="ar" dir="rtl"> <!-- French --> <html lang="fr">
WCAG 2.1 AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18px+ bold or 24px+ regular). Use a contrast checker before shipping.
/* โ #6b7280 on #fff = 4.48:1 (fails AA for small text) */ .muted { color: #6b7280; background: #fff; } /* โ #4b5563 on #fff = 7.0:1 (passes AA & AAA) */ .muted { color: #4b5563; background: #fff; }
Sometimes you need text only screen readers can hear โ like a skip-to-content link or a label for a visual element. The .sr-only utility class hides it visually while keeping it accessible.
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } <!-- Skip link example --> <a href="#main" class="sr-only">Skip to content</a>
When a modal is open, keyboard focus must be confined within it. Otherwise Tab will focus elements behind the overlay โ making the modal unusable for keyboard users.
function trapFocus(modal) { const focusable = modal.querySelectorAll( 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; modal.addEventListener('keydown', e => { if (e.key !== 'Tab') return; if (e.shiftKey ? document.activeElement === first : document.activeElement === last) { e.preventDefault(); (e.shiftKey ? last : first).focus(); } }); first.focus(); }
Responsive & Mobile Tips
Mobile traffic is dominant. These tips ensure your designs work beautifully across every screen size โ from a 320px phone to a 4K monitor.
Without the viewport meta tag, mobile browsers render at ~980px desktop width and then zoom out. Allow user scaling โ disabling it is a major accessibility barrier and breaks browser zoom.
<!-- โ Disables user zoom (accessibility violation) --> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <!-- โ Correct: allows scaling up to 5ร --> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5">
Apple's Human Interface Guidelines and WCAG 2.5.5 recommend a minimum 44ร44 CSS pixel tap target. Small targets frustrate users and lead to mis-taps. You can expand the clickable area without changing the visual size using padding or pseudo-elements.
/* Expand tap area without visual change */ .icon-btn { position: relative; width: 24px; height: 24px; } .icon-btn::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 44px; min-height: 44px; }
CSS logical properties like margin-inline-start instead of margin-left automatically flip direction based on the document's writing mode โ making RTL support almost effortless.
/* Physical (doesn't flip for RTL) */ .icon { margin-right: 8px; } /* Logical (flips automatically in RTL) */ .icon { margin-inline-end: 8px; } /* More logical property examples */ .card { padding-inline: 16px; /* left + right */ padding-block: 12px; /* top + bottom */ border-start-start-radius: 12px; /* top-left in LTR */ }
100vh on mobile browsers includes the browser chrome (address bar), causing overflow when the bar appears. The new dvh (dynamic viewport height) unit adjusts as the browser chrome shows or hides.
/* โ Overflows on mobile when browser chrome appears */ .hero { height: 100vh; } /* โ With fallback for older browsers */ .hero { height: 100vh; /* fallback */ height: 100dvh; /* dynamic viewport height */ }
svh (small viewport, always excludes chrome) and lvh (large viewport, always includes chrome space).More from ResponsiveCheckTool
Put These Tips Into Practice
Test how your responsive changes look across mobile, tablet, and desktop โ instantly, for free, no sign-up needed.
Test Your Website Now