Vanilla JS Slider

A drag-and-snap carousel in about 4 KB. Cards size to their own content, the track snaps to each one, and it works with a mouse, a finger, a trackpad and a keyboard.

  • Vanilla JS
  • No dependencies
  • Drag & swipe
  • Accessible

Try it

Drag with the mouse, swipe, scroll sideways on a trackpad, or focus a slider and use ← →.

Cards fit their content every card is a different width

width: max-content
  • metricLCP
  • metricCumulative Layout Shift
  • metricTTFB
  • metricInteraction to Next Paint
  • metricSpeed Index
  • metricFCP
  • metricTotal Blocking Time

Uniform cards with arrows fixed width, links inside

width: 17rem

Site types

Dark surface inverted dots and arrows

.bg-dark

How a speed fix goes

  • step 1Audit
  • step 2Strip the bloat
  • step 3Pre-render
  • step 4Ship to the edge
  • step 5Measure
  • step 6Keep it fast

Small, and it still sweats the details.

Version 2 is a rewrite. Everything below is tested in a real browser, on the readable and the minified build.

  • Snaps to real positions

    Each card lands flush left, whatever its width. Near the end the track stops and the active card walks on to the last one — every card gets its dot and its turn.

  • Mouse, touch, pen, trackpad

    Pointer Events for mouse and pen, native touch events for fingers so iPhone Safari never steals the swipe. A flick carries on with its speed, and a sideways two-finger swipe works on a trackpad.

  • Never fights the page

    A vertical swipe still scrolls the page. Dragging over a link doesn't open it, and form fields inside cards keep their arrow keys.

  • Keyboard and screen readers

    Arrow keys, Home and End, a focus that brings its card into view, labelled buttons and dots, and a quiet "Slide 2 of 5" announcement.

  • Keeps itself in sync

    It re-measures when images or fonts load and when the layout changes, not only on window resize. No hard-coded slide width anywhere.

  • Plays well with others

    One global name, an API with goTo() and destroy(), a slider:change event — and v1 markup keeps working as it is.

Add it to your site.

Three pieces: the markup, a little CSS and the script. Put several sliders on one page — each runs on its own.

  1. Markup

    The script looks for data-slider with a viewport and a track inside. Every child of the track is a slide. Arrows and dots are optional.

    HTML
    <section class="slider" data-slider aria-label="Recent work">
      <div class="slider-head">
        <h2>Recent work</h2>
        <!-- Optional: arrows -->
        <div class="slider-arrows">
          <button class="slider-btn" data-slider-prev aria-label="Previous slide">←</button>
          <button class="slider-btn" data-slider-next aria-label="Next slide">→</button>
        </div>
      </div>
    
      <div class="slider-viewport" data-slider-viewport>
        <ul class="slider-track" data-slider-track>
          <li class="slider-card">Card 1</li>
          <li class="slider-card">Card 2</li>
          <li class="slider-card">Card 3</li>
          <li class="slider-card">Card 4</li>
        </ul>
      </div>
    
      <!-- Optional: the script fills this with dots -->
      <div class="slider-pagination" data-slider-dots></div>
    </section>

  2. CSS

    Base styles to restyle freely. .slider-card is max-content wide — give it a fixed width for uniform cards.

    CSS
    /*! LeggoSlider v2.1 — base styles | leggo.dev/tools/slider | MIT
       ─────────────────────────────────────────────────────────────────
       What to change where:
         gap between cards ............. .slider-track  → gap
         card width .................... .slider-card   → width (max-content = fits its content)
         arrow buttons ................. .slider-btn    → size, border, colours
         dots .......................... .slider-dot    → size, colour; current one is .is-active
         dark background ............... .bg-dark …     → dot and arrow colours
       No z-index is used.
       ───────────────────────────────────────────────────────────────── */
    
    .slider-viewport {
        overflow: hidden;
        touch-action: pan-y;               /* horizontal swipes are ours, vertical ones scroll the page */
        cursor: grab;
        -webkit-user-select: none;
        user-select: none;
    }
    
    [data-slider-dragging="true"] .slider-viewport {
        cursor: grabbing;
    }
    
    .slider-viewport:focus-visible {
        outline: 2px solid #2B5CFF;
        outline-offset: 4px;
        border-radius: 4px;
    }
    
    .slider-track {
        display: flex;
        gap: 1.25rem;
        margin: 0;
        padding: 0;
        list-style: none;
        will-change: transform;
    }
    
    .slider-card {
        flex: 0 0 auto;
        width: max-content;                /* each card is as wide as its content */
        max-width: min(80vw, 24rem);
    }
    
    .slider-card img {
        -webkit-user-drag: none;
    }
    
    /* Arrows */
    
    .slider-arrows {
        display: flex;
        gap: 0.5rem;
    }
    
    .slider-btn {
        display: grid;
        place-items: center;
        width: 2.75rem;
        height: 2.75rem;
        padding: 0;
        border: 1px solid currentColor;
        border-radius: 100px;
        background: transparent;
        color: inherit;
        font: inherit;
        line-height: 1;
        cursor: pointer;
        transition: background-color 0.2s ease, color 0.2s ease, opacity 0.2s ease;
    }
    
    .slider-btn:hover {
        background-color: #0D1109;
        color: #FFFFFF;
    }
    
    .slider-btn:focus-visible {
        outline: 2px solid #2B5CFF;
        outline-offset: 2px;
    }
    
    .slider-btn.is-disabled {
        opacity: 0.25;
        cursor: default;
        pointer-events: none;
    }
    
    /* Dots */
    
    .slider-pagination {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.25rem;
        margin-top: 1.5rem;
    }
    
    .slider-pagination[hidden] {
        display: none;
    }
    
    /* The dot is 10px, the hit area 24px — big enough for a finger and WCAG 2.5.8. */
    .slider-dot {
        position: relative;
        width: 1.5rem;
        height: 1.5rem;
        padding: 0;
        border: 0;
        background: none;
        cursor: pointer;
    }
    
    .slider-dot::before {
        content: "";
        position: absolute;
        inset: 0;
        width: 0.625rem;
        height: 0.625rem;
        margin: auto;
        border-radius: 50%;
        background-color: rgba(13, 17, 9, 0.22);
        transition: background-color 0.3s ease, transform 0.3s ease;
    }
    
    .slider-dot:hover::before {
        background-color: rgba(13, 17, 9, 0.5);
    }
    
    .slider-dot.is-active::before {
        background-color: #0D1109;
        transform: scale(1.3);
    }
    
    .slider-dot:focus-visible {
        outline: 2px solid #2B5CFF;
        outline-offset: 0;
        border-radius: 50%;
    }
    
    /* Nothing to scroll — no arrows or dots needed */
    [data-slider-static="true"] .slider-arrows {
        visibility: hidden;
    }
    
    /* Dark background */
    
    .bg-dark .slider-dot::before {
        background-color: rgba(255, 255, 255, 0.25);
    }
    
    .bg-dark .slider-dot.is-active::before {
        background-color: #FFFFFF;
    }
    
    .bg-dark .slider-btn:hover {
        background-color: #FFFFFF;
        color: #0D1109;
    }
    
    @media (prefers-reduced-motion: reduce) {
        .slider-track,
        .slider-btn,
        .slider-dot::before {
            transition-duration: 0.01ms !important;
        }
    }

  3. JavaScript

    Load it once, with defer. It starts every slider on the page by itself.

    JavaScript
    /*! LeggoSlider v2.2 | leggo.dev/tools/slider | MIT */
    ((t,e)=>{"use strict";const i={speed:500,easing:"cubic-bezier(0.25, 0.46, 0.45, 0.94)",threshold:40,labels:{region:"Carousel",prev:"Previous slide",next:"Next slide",dots:"Choose slide",dot:"Go to slide %n",status:"Slide %n of %t"}},s="[data-slider], .slider-instance",r=new WeakMap,o=t.matchMedia("(prefers-reduced-motion: reduce)"),h='input, textarea, select, [contenteditable=""], [contenteditable="true"]';let a=0;class n{#t;#e;#i;#s;#r=!1;#o;#h;#a;#n;#l=[];#d=[];#c=[];#u=!0;#p=0;#g=0;#v=0;#b;#x=null;#m=null;#f=!1;#y=0;#w=0;#k=null;#A=new Map;#T={};constructor(t,e={}){if(!t)throw new Error("LeggoSlider: root element not passed.");const s=r.get(t);if(s)return s;if(this.#t=t,this.#e=t.querySelector("[data-slider-viewport], .slider-viewport"),this.#i=t.querySelector("[data-slider-track], .slider-track"),this.#s=this.#i?.getAttribute("style")??null,!this.#e||!this.#i)throw new Error("LeggoSlider: viewport or track missing.");this.#b={...i,...this.#M(t),...e,labels:{...i.labels,...e.labels||{}}},this.#o=this.#S(t.querySelector("[data-slider-prev], .prev-btn")),this.#h=this.#S(t.querySelector("[data-slider-next], .next-btn")),this.#a=this.#S(t.querySelector("[data-slider-dots], .slider-pagination")),this.#r=!!this.#a?.hidden,this.#E(),this.#C(),this.refresh(),r.set(t,this),this.#B(t,"data-slider-ready","")}get index(){return this.#g}get length(){return this.#l.length}next(){this.goTo(this.#g+1)}prev(){this.goTo(this.#g-1)}goTo(t,{animate:e=!0,announce:i=!0}={}){const s=Math.max(0,Math.min(this.#l.length-1,t)),r=s!==this.#g;this.#g=s,this.#L(this.#c[s]??0,e),this.#D(),r&&(i&&this.#I(),this.#X("slider:change",{index:s,slide:this.#l[s]||null}))}refresh(){this.#l.forEach(t=>this.#k?.unobserve(t)),this.#l=Array.from(this.#i.children).filter(t=>1===t.nodeType),this.#l.forEach(t=>this.#k?.observe(t)),this.#q(),this.#N(),this.#g=Math.min(this.#g,Math.max(0,this.#l.length-1)),this.#L(this.#c[this.#g]??0,!1),this.#D()}destroy(){this.#w&&cancelAnimationFrame(this.#w),clearTimeout(this.#y),this.#k?.disconnect(),this.#O().forEach(([t,e,i,s])=>t?.removeEventListener(e,i,s)),this.#d.forEach(t=>t.remove()),this.#d=[],this.#n?.remove(),this.#a&&(this.#a.hidden=this.#r),this.#i.style.transform="",this.#i.style.transition="",null!==this.#s||this.#i.getAttribute("style")||this.#i.removeAttribute("style");const t=["is-active","is-next","is-prev"];this.#l.forEach(e=>e.classList.remove(...t)),[this.#o,this.#h].forEach(t=>t?.classList.remove("is-disabled")),this.#A.forEach((t,e)=>{Object.entries(t).forEach(([t,i])=>{null===i?e.removeAttribute(t):e.setAttribute(t,i)})}),this.#A.clear(),r.delete(this.#t)}static get(t){return r.get(t)||null}static init(t,i=e){return Array.from(i.querySelectorAll(s),e=>{try{return new n(e,t)}catch(t){return null}}).filter(Boolean)}#M(t){const e={},i=(t,e,i)=>{const s=parseFloat(t);return Number.isNaN(s)?void 0:Math.min(i,Math.max(e,s))},s=i(t.dataset.sliderSpeed,0,5e3),r=i(t.dataset.sliderThreshold,0,400);return void 0!==s&&(e.speed=s),void 0!==r&&(e.threshold=r),e}#S(t){return!t||this.#i.contains(t)?null:t.closest(s)===this.#t?t:null}#B(t,e,i){if(!t)return;let s=this.#A.get(t);s||(s={},this.#A.set(t,s)),e in s||(s[e]=t.getAttribute(e)),t.getAttribute(e)!==i&&t.setAttribute(e,i)}#E(){const t=this.#b.labels,i=this.#t;if(i.hasAttribute("role")||/^(SECTION|ASIDE|NAV)$/.test(i.tagName)||this.#B(i,"role","region"),this.#B(i,"aria-roledescription","carousel"),!i.hasAttribute("aria-label")&&!i.hasAttribute("aria-labelledby")){const e=Array.from(i.querySelectorAll("h1,h2,h3,h4,h5,h6")).find(t=>!this.#i.contains(t));e?(e.id||this.#B(e,"id","leggo-slider-title-"+ ++a),this.#B(i,"aria-labelledby",e.id)):this.#B(i,"aria-label",t.region)}this.#i.id||this.#B(this.#i,"id","leggo-slider-track-"+ ++a),this.#e.hasAttribute("tabindex")||this.#B(this.#e,"tabindex","0"),"auto"===getComputedStyle(this.#e).touchAction&&this.#B(this.#e,"style",`${this.#e.getAttribute("style")||""};touch-action:pan-y`.replace(/^;/,"")),this.#F(this.#o,t.prev),this.#F(this.#h,t.next),this.#a&&(this.#a.hasAttribute("role")||this.#B(this.#a,"role","group"),this.#a.hasAttribute("aria-label")||this.#B(this.#a,"aria-label",t.dots)),this.#n=e.createElement("p"),this.#n.setAttribute("aria-live","polite"),this.#n.setAttribute("aria-atomic","true"),this.#n.style.cssText="position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0",i.appendChild(this.#n)}#F(t,e){t&&("BUTTON"!==t.tagName||t.hasAttribute("type")||this.#B(t,"type","button"),t.hasAttribute("aria-label")||/[a-z]{2,}/i.test(t.textContent)||this.#B(t,"aria-label",e),this.#B(t,"aria-controls",this.#i.id))}#O(){const t=this.#T,e=this.#e,i=this.#i;return[ [e,"pointerdown",t.down],[e,"pointermove",t.move],[e,"pointerup",t.up],[e,"pointercancel",t.up],[e,"lostpointercapture",t.up],[e,"touchstart",t.tstart,{passive:!0}],[e,"touchmove",t.tmove,{passive:!1}],[e,"touchend",t.tend],[e,"touchcancel",t.tend],[e,"wheel",t.wheel,{passive:!1}],[e,"scroll",t.scroll,{passive:!0}],[i,"click",t.click,!0],[i,"dragstart",t.dragstart],[i,"focusin",t.focusin],[this.#t,"keydown",t.key],[this.#o,"click",t.prev],[this.#h,"click",t.next],[this.#a,"click",t.dot] ]}#C(){const e=this.#T;e.down=this.#R.bind(this),e.move=this.#U.bind(this),e.up=this.#H.bind(this),e.tstart=this.#K.bind(this),e.tmove=this.#P.bind(this),e.tend=this.#W.bind(this),e.wheel=this.#Y.bind(this),e.key=this.#$.bind(this),e.focusin=this.#z.bind(this),e.prev=()=>this.prev(),e.next=()=>this.next(),e.dot=t=>{const e=t.target.closest("[data-slider-dot]");e&&this.#d.includes(e)&&this.goTo(this.#d.indexOf(e))},e.click=t=>{this.#f&&(t.preventDefault(),t.stopPropagation(),this.#f=!1)},e.dragstart=t=>t.preventDefault(),e.scroll=()=>{this.#e.scrollLeft&&(this.#e.scrollLeft=0)},this.#O().forEach(([t,e,i,s])=>t?.addEventListener(e,i,s)),"ResizeObserver"in t&&(this.#k=new ResizeObserver(()=>{this.#w||(this.#w=requestAnimationFrame(()=>{if(this.#w=0,this.#x)return;const t=this.#u;this.#q(),t===this.#u&&this.#d.length===this.#l.length||this.#N(),this.#L(this.#c[this.#g]??0,!1),this.#D()}))}),this.#k.observe(this.#e),this.#k.observe(this.#i))}#q(){const t=this.#i.getBoundingClientRect().left,e=parseFloat(getComputedStyle(this.#i).paddingLeft)||0;this.#p=Math.min(0,this.#e.clientWidth-this.#i.scrollWidth),this.#c=this.#l.map(i=>{const s=i.getBoundingClientRect().left-t-e;return Math.max(this.#p,Math.min(0,-s))}),this.#u=this.#p>-1,this.#B(this.#t,"data-slider-static",String(this.#u))}#L(t,e){this.#v=t;const i=this.#i.style;i.transition=e&&!o.matches&&this.#b.speed>0?`transform ${this.#b.speed}ms ${this.#b.easing}`:"none",i.transform=`translate3d(${t}px, 0, 0)`}#j(t){return t>0?.35*t:t<this.#p?this.#p+.35*(t-this.#p):t}#G(t,e){const i=this.#v+180*e,s=t<0?1:-1;let r=this.#c[0]??0;this.#c.forEach(t=>{Math.abs(t-i)<Math.abs(r-i)&&(r=t)});const o=this.#c.reduce((t,e,i)=>Math.abs(e-r)<1?t.concat(i):t,[]);let h;h=o.includes(this.#g)?Math.abs(t)>this.#b.threshold?this.#g+s:this.#g:s>0?o[0]:o[o.length-1],this.goTo(h,{announce:!1})}#V(t,e){this.#u||this.#x||e?.closest?.(h)||(this.#x={id:t.id,x0:t.x,y0:t.y,base:this.#v,axis:null,lastX:t.x,lastT:t.t,v:0},this.#f=!1)}#J(t,e){const i=this.#x;if(!i||t.id!==i.id)return!1;const s=t.x-i.x0,r=t.y-i.y0;if(!i.axis){if(Math.abs(s)<6&&Math.abs(r)<6)return!1;if(i.axis=Math.abs(s)>Math.abs(r)?"x":"y","y"===i.axis)return this.#x=null,!1;e?.(),this.#B(this.#t,"data-slider-dragging","true")}const o=Math.max(1,t.t-i.lastT);return i.v=.6*i.v+(t.x-i.lastX)/o*.4,i.lastX=t.x,i.lastT=t.t,this.#L(this.#j(i.base+s),!1),this.#v=i.base+s,!0}#Q(t,e){const i=this.#x;if(!i||t.id!==i.id)return;if(this.#x=null,"x"!==i.axis)return;this.#B(this.#t,"data-slider-dragging","false");const s=(e?i.lastX:t.x)-i.x0;Math.abs(s)>6&&(this.#f=!0),setTimeout(()=>{this.#f=!1},0),this.#v=this.#j(this.#v),this.#G(s,e?0:i.v)}#R(t){"touch"!==t.pointerType&&t.isPrimary&&0===t.button&&this.#V({id:"p"+t.pointerId,x:t.clientX,y:t.clientY,t:t.timeStamp},t.target)}#U(t){"touch"!==t.pointerType&&this.#J({id:"p"+t.pointerId,x:t.clientX,y:t.clientY,t:t.timeStamp},()=>{try{this.#e.setPointerCapture(t.pointerId)}catch(t){}})}#H(t){"touch"!==t.pointerType&&("lostpointercapture"===t.type&&t.target!==this.#e||this.#Q({id:"p"+t.pointerId,x:t.clientX},"pointercancel"===t.type))}#K(t){if(t.touches.length>1)return void(this.#x&&this.#Q({id:this.#x.id,x:this.#x.lastX},!0));const e=t.changedTouches[0];this.#V({id:"t"+e.identifier,x:e.clientX,y:e.clientY,t:t.timeStamp},t.target)}#Z(t){const e=this.#x&&[...t.changedTouches].find(t=>"t"+t.identifier===this.#x.id);return e&&{id:this.#x.id,x:e.clientX,y:e.clientY,t:t.timeStamp}}#P(t){const e=this.#Z(t);e&&this.#J(e)&&t.cancelable&&t.preventDefault()}#W(t){const e=this.#Z(t);e&&this.#Q(e,"touchcancel"===t.type)}#Y(t){this.#u||Math.abs(t.deltaX)<=Math.abs(t.deltaY)||(t.preventDefault(),this.#m||(this.#m={start:this.#v}),this.#v=Math.max(this.#p-80,Math.min(80,this.#v-t.deltaX)),this.#L(this.#j(this.#v),!1),this.#v=this.#j(this.#v),clearTimeout(this.#y),this.#y=setTimeout(()=>{const t=this.#v-this.#m.start;this.#m=null,this.#G(t,0)},140))}#$(t){if(t.defaultPrevented||t.altKey||t.ctrlKey||t.metaKey)return;if(t.target.closest(h))return;const e={ArrowLeft:this.#g-1,ArrowRight:this.#g+1,Home:0,End:this.#l.length-1};t.key in e&&("Home"!==t.key&&"End"!==t.key||t.target===this.#e)&&(t.preventDefault(),this.goTo(e[t.key]))}#z(t){const e=this.#l.find(e=>e.contains(t.target));if(!e)return;this.#e.scrollLeft=0;const i=this.#e.getBoundingClientRect(),s=e.getBoundingClientRect();s.left>=i.left-1&&s.right<=i.right+1||this.goTo(this.#l.indexOf(e),{announce:!1})}#N(){this.#a&&(this.#a.querySelectorAll("[data-slider-dot], .slider-dot").forEach(t=>t.remove()),this.#d=this.#u?[]:this.#l.map((t,i)=>{const s=e.createElement("button");return s.type="button",s.className="slider-dot",s.setAttribute("data-slider-dot",""),s.setAttribute("aria-label",this.#b.labels.dot.replace("%n",i+1)),this.#a.appendChild(s),s}),this.#a.hidden=this.#u)}#D(){const t=this.#l.length-1;this.#_(this.#o,this.#u||this.#g<=0),this.#_(this.#h,this.#u||this.#g>=t),this.#d.forEach((t,e)=>{const i=e===this.#g;t.classList.toggle("is-active",i),i?t.setAttribute("aria-current","true"):t.removeAttribute("aria-current")});const e=this.#g;this.#l.forEach((t,i)=>{t.classList.toggle("is-active",i===e),t.classList.toggle("is-next",i===e+1),t.classList.toggle("is-prev",i===e-1)})}#_(t,e){t&&(t.classList.toggle("is-disabled",e),this.#B(t,"aria-disabled",String(e)))}#I(){const t=this.#b.labels.status.replace("%n",this.#g+1).replace("%t",this.#l.length);this.#n.textContent="",requestAnimationFrame(()=>{this.#n&&(this.#n.textContent=t)})}#X(t,e){this.#t.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:{slider:this,...e}}))}}"loading"===e.readyState?e.addEventListener("DOMContentLoaded",()=>n.init(),{once:!0}):n.init(),t.LeggoSlider=n})(window,document);

Hooks and API.

Nothing to configure — style the state classes, and reach for the API only when you need it.

Name Where What it does
.is-active card, dot The card at the left edge and its dot.
.is-next / .is-prev card The cards either side of the active one.
.is-disabled arrow The track is at that end. The button also gets aria-disabled="true" and keeps its focus.
data-slider-dragging slider "true" while someone drags — handy for a grabbing cursor or pausing hover effects.
data-slider-static slider "true" when every card fits. Arrows hide, dots aren't drawn.
data-slider-speed slider Transition time in ms, default 500. 0 jumps.
data-slider-threshold slider How far a slow drag must go to change slides, default 40px.
LeggoSlider.get(el) JS The instance for a slider element, then .goTo(i), .next(), .prev(), .index, .length.
.refresh() / .destroy() JS Re-read the cards after adding some; take the slider off and leave the markup exactly as it was.
LeggoSlider.init(options, scope) JS Start sliders added later, e.g. after an AJAX load. Calling it twice is safe.
slider:change event Fires on the slider with detail.index and detail.slide. It bubbles.

Questions about the slider.

Does it need jQuery or any library?

No. It's plain JavaScript with no dependencies — about 4 KB gzipped. Drop the file in with defer and it starts every slider on the page.

Can I have several sliders on one page?

Yes. Each data-slider block runs on its own, with its own arrows, dots and position. Sliders added later — after an AJAX load, say — start with LeggoSlider.init().

Does it work in Webflow, WordPress and Builderius?

Yes. It only needs the markup, the CSS and the script, so it fits any builder or theme. In Builderius, paste the markup through the HTML → Builderius converter and add the script as a JS snippet.

I use the old version. Do I have to change my HTML?

No. The v1 classes — .slider-instance, .slider-track, .prev-btn, .next-btn, .slider-pagination — still work. Swap the script and CSS, and dots saved into the page by a builder are replaced automatically.

Is it accessible?

It's built for it: the slider is announced as a carousel with a name, arrows and dots are labelled buttons, arrow keys and Home/End work, tabbing into a hidden card brings it into view, and the current slide is announced politely. With reduced motion turned on, it moves without animation.

Why no autoplay or infinite loop?

On purpose. Autoplay moves content while people are reading it and is a common accessibility complaint; a loop hides where the list ends. If a project really needs either, goTo() and the slider:change event make it a few lines of code.

Can I use it in client projects?

Yes. It's free and MIT-licensed — keep the one-line comment at the top of the file.

Need a slider, or a whole site, that's fast on real phones?

Get a quote