swiper: [swiper/react] Custom navigation/pagination components using React refs not working/possible?

  • Swiper Version: 6.2.0
  • Platform/Target and Browser Versions: All

Also posten on Stackoverflow: Swiper React | How to create custom navigation/pagination components using React refs?

What you did

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)

  return (
    <Swiper
      navigation={{
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}

Expected Behavior

To work

Actual Behavior

Did not work


SwiperJS documentation states that navigation prevEl/nextEl can either be of type “string” or “HTMLElement”. Using HTML nodes allows for navigation prevEl/nextEl to be scoped to each rendered instance of MySwiper. In React this is usually done with “refs”.

const App = () => (
  <div>
    <MySwiper /> // MySwiper1
    <MySwiper /> // MySwiper2
  </div>
)

In the App example above, navigation prevEl/nextEl from “MySwiper2” should not trigger sliding of “MySwiper1”, which is what would happen if one would have used string selectors like { prevEl: '.prev', nextEl: '.next' }. Obviously (if even possible within the application) one could generate unique classnames. A better solution would be to pass the HTML elements as these are already unique. Is this possible somehow with React refs?

My current hacky workaround:

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)

  return (
    <Swiper
      navigation={{
        // Both prevEl & nextEl are null at render so this does not work
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
      onSwiper={(swiper) => {
        // Delay execution for the refs to be defined
        setTimeout(() => {
          // Override prevEl & nextEl now that refs are defined
          swiper.params.navigation.prevEl = navigationPrevRef.current
          swiper.params.navigation.nextEl = navigationNextRef.current

          // Re-init navigation
          swiper.navigation.destroy()
          swiper.navigation.init()
          swiper.navigation.update()
        })
      }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}

Thanks in advance!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 10
  • Comments: 74 (1 by maintainers)

Most upvoted comments

I was having a hard time getting some of the above examples to work. I ended up using the slidePrev() and slideNext() functions like this:

const swiperRef = React.useRef(null);

return (
  <>
    <div id="previousButton" onClick={() => swiperRef.current.swiper.slidePrev()} />
    <div id="nextButton" onClick={() => swiperRef.current.swiper.slideNext()} />
    <Swiper
      ref={swiperRef}
    >
      <SwiperSlide>
        Slide 1
      </SwiperSlide>
      <SwiperSlide>
        Slide 2
      </SwiperSlide>
    </Swiper>
  </>
)

Another way to solve this problem:

// use the `useState` hook instead of `useRef`
const [prevEl, setPrevEl] = useState<HTMLElement | null>(null)
const [nextEl, setPrevEl] = useState<HTMLElement | null>(null)
<Swiper navigation={{ prevEl, nextEl }}>
// some slides
</Swiper>
<div ref={(node) => setPrevEl(node)}>prev</div>
<div ref={(node) => setNextEl(node)}>next</div>

Not possible by passing directly as refs are null initially, so it is possible with your approach or similar:

  const prevRef = useRef(null);
  const nextRef = useRef(null);
  return (
    <Swiper
      onInit={(swiper) => {
        swiper.params.navigation.prevEl = prevRef.current;
        swiper.params.navigation.nextEl = nextRef.current;
        swiper.navigation.init();
        swiper.navigation.update();
      }}
    >
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <div ref={prevRef}>Prev</div>
      <div ref={nextRef}>Next</div>
    </Swiper>
  );

In case this helps anyone using Swiper with TypeScript:

import SwiperCore from 'swiper';
import {NavigationOptions} from 'swiper/types/components/navigation';
import {PaginationOptions} from 'swiper/types/components/pagination';

const CustomSwiper = () => {
  const navPrevButton = React.useRef<HTMLButtonElement>(null);
  const navNextButton = React.useRef<HTMLButtonElement>(null);
  const paginationLabel = React.useRef<HTMLHeadingElement>(null);

  const onBeforeInit = (Swiper: SwiperCore): void => {
    if (typeof Swiper.params.navigation !== 'boolean') {
      const navigation = Swiper.params.navigation;
      navigation.prevEl = navPrevButton.current;
      navigation.nextEl = navNextButton.current;
    }

    if (typeof Swiper.params.pagination !== 'boolean') {
      Swiper.params.pagination.el = paginationLabel.current;
    }
  };

 return (
    <Swiper onBeforeInit={onBeforeInit}>
      <SwiperSlide>1</SwiperSlide>
      <SwiperSlide>2</SwiperSlide>
      <SwiperSlide>3</SwiperSlide>
      <button ref={navPrevButton} />
      <button ref={navNextButton} />
    </Swiper>
  )
}

No need to define navigation/pagination props on Swiper unless you need/want to override other things.

@mistval this works, but you will lose the automatic state handling of the navigation buttons (css classes like .swiper-button-disabled etc).

I use this simple react hook to solve this:

// useSwiperRef.js
import { useState, useRef, useEffect } from 'react';

const useSwiperRef = () => {
    const [wrapper, setWrapper] = useState(null);
    const ref = useRef(null);

    useEffect(() => {
        setWrapper(ref.current);
    }, []);

    return [
        wrapper,
        ref
    ]
};

export default useSwiperRef;

and then use it like this:

import useSwiperRef from 'useSwiperRef.js';

const Slider = () => {

    const [nextEl, nextElRef] = useSwiperRef();
    const [prevEl, prevElRef] = useSwiperRef();

    return (
        <Container>
            <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >
                <SwiperSlide>1</SwiperSlide>
                <SwiperSlide>2</SwiperSlide>
                <SwiperSlide>3</SwiperSlide>
            </Swiper>
            <Nav ref={prevElRef} />
            <Nav ref={nextElRef} />
        </Container>
    );
};

you should be able to use this hook with everything ref related, for example with the pagination:

import useSwiperRef from 'useSwiperRef.js';

const Slider = () => {

    const [paginationEl, paginationRef] = useSwiperRef();

    return (
        <Container>
            <Swiper
                pagination={{
                    el: paginationEl
                }}
            >
                <SwiperSlide>1</SwiperSlide>
                <SwiperSlide>2</SwiperSlide>
                <SwiperSlide>3</SwiperSlide>
            </Swiper>
            <Pagination ref={paginationRef} />
        </Container>
    );
};

@danvisintainer sure:

// Foobar.js
import { Swiper, SwiperSlide } from "swiper/react";
import SwiperCore, { Navigation } from "swiper";

import Image from "next/image";
import { useRef } from "react";

SwiperCore.use([Navigation]);

export default function Foobar({ images }) {
  const prevRef = useRef(null);
  const nextRef = useRef(null);

  return (
    <section>
      <h2 className="mb-10">foobar</h2>

      <Swiper
        spaceBetween={16}
        slidesPerView={3}
        loop
        navigation={{
          prevEl: prevRef.current ? prevRef.current : undefined,
          nextEl: nextRef.current ? nextRef.current : undefined,
        }}
        onInit={(swiper) => {
          swiper.params.navigation.prevEl = prevRef.current;
          swiper.params.navigation.nextEl = nextRef.current;
          swiper.navigation.update();
        }}
        breakpoints={{
          320: {
            slidesPerView: 1.5,
          },
          991: {
            slidesPerView: 3,
          },
        }}
      >
        {images.map((image, i) => (
          <SwiperSlide key={`slide_${i}`}>
            <Image
              src={`${process.env.NEXT_PUBLIC_IMAGE_PATH}/${image.path}`}
              width="400"
              height="300"
            />
          </SwiperSlide>
        ))}
        <div className="flex flex-row justify-between mt-5 md:mt-10 md:px-8">
          <div ref={prevRef} className="cursor-pointer">
            <Image
              src="/images/icons/arrow-left-circle-orange.svg"
              height="62"
              width="62"
            />
          </div>
          <div ref={nextRef} className="cursor-pointer">
            <Image
              src="/images/icons/arrow-right-circle-orange.svg"
              height="62"
              width="62"
            />
          </div>
        </div>
      </Swiper>
    </section>
  );
}

Could anybody show me full code? (and if you could Typescript?) I’m strugging making this.

swiper.params.navigation.prevEl = prevRef.current; in onInit can be error:

TS2339: Property 'prevEl' does not exist on type 'boolean | NavigationOptions'.
Property 'prevEl' does not exist on type 'false'.

@rikusen0335 @nolimits4web I’d also appreciate some React/TypeScript help here:

The example below works, but only because of the @ts-ignore due to the type error in the comment above.

const prevRef = useRef<HTMLDivElement>(null)
const nextRef = useRef<HTMLDivElement>(null)

return (
  <Swiper
    navigation={{
      prevEl: prevRef.current ? prevRef.current : undefined,
      nextEl: nextRef.current ? nextRef.current : undefined,
    }}
    onInit={swiper => {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line no-param-reassign
      swiper.params.navigation.prevEl = prevRef.current
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line no-param-reassign
      swiper.params.navigation.nextEl = nextRef.current
      swiper.navigation.update()
    }}
  >
    <SwiperSlide>1</SwiperSlide
    <SwiperSlide>2</SwiperSlide
    <div ref={prevRef}>Prev</div>
    <div ref={nextRef}>Next</div>
  </Swiper>
)

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

Type 'MutableRefObject<null> | null' is not assignable to type 'HTMLElement | CSSSelector | null | undefined'.
  Type 'MutableRefObject<null>' is not assignable to type 'HTMLElement | CSSSelector | null | undefined'.
    Type 'MutableRefObject<null>' is missing the following properties from type 'CSSSelector': charAt, charCodeAt, concat, indexOf, and 43 more.

How to solve this?

const useSwiperRef = <T extends HTMLElement>(): [
  T | undefined,
  React.Ref<T>
] => {
  const [wrapper, setWrapper] = useState<T>();
  const ref = useRef<T>(null);

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current);
    }
  }, []);

  return [wrapper, ref];
};

and then we could use it:

const [paginationEle, paginationRef] = useSwiperRef<HTMLDivElement>();

TypeScript solution

With the previous solution, the buttons might not work. You need to tweak it a bit. Be sure to initialize the state with null:

const useSwiperRef = <T extends HTMLElement>(): [T | null, React.Ref<T>] => {
  const [wrapper, setWrapper] = useState<T | null>(null)
  const ref = useRef<T>(null)

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current)
    }
  }, [])

  return [wrapper, ref]
}
  const [nextEl, nextElRef] = useSwiperRef<HTMLButtonElement>()
  const [prevEl, prevElRef] = useSwiperRef<HTMLButtonElement>()
...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
        }}
      >
      ...
  </Swiper>
  <Button ref={prevElRef} />
  <Button ref={nextElRef} />
  ...

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!

Typescript solution

hey guys, I think I fixed the issue, I also faced the same problem, but finally, let’s start 1 - import SwiperCore, { Navigation} from ‘swiper’ 2 - SwiperCore.use([Navigation]) 3 - i will use your exmaple:

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)

  return (
    <Swiper
      navigation={{
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
     onBeforeInit={{
          swiper.params.navigation.prevEl = navigationPrevRef.current;
          swiper.params.navigation.nextEl = navigationNextRef.current;
     }}

    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}

that’s it, so if you check Swiper duc there is a page only for API, where you can find a section talking about events that swiper provide, anyway i hope this was helpful

@rikusen0335 I played around with this a bit more (still in TypeScript) and got the following to work for custom navigation, with refs (so as not to use className):

"swiper": "^6.3.5",
const prevRef = useRef<HTMLDivElement>(null)
const nextRef = useRef<HTMLDivElement>(null)

return (
  <Swiper
     navigation={{
      prevEl: navigationPrev.current!, // Assert non-null
      nextEl: navigationNext.current!, // Assert non-null
    }}
  >
    <SwiperSlide>1</SwiperSlide
    <SwiperSlide>2</SwiperSlide
    <div ref={prevRef}>Prev</div>
    <div ref={nextRef}>Next</div>
  </Swiper>
)

This works for me, without having to call the onInit function and assign the params.

I was having a hard time getting some of the above examples to work. I ended up using the slidePrev() and slideNext() functions like this:

const swiperRef = React.useRef(null);

return (
  <>
    <div id="previousButton" onClick={() => swiperRef.current.swiper.slidePrev()} />
    <div id="nextButton" onClick={() => swiperRef.current.swiper.slideNext()} />
    <Swiper
      ref={swiperRef}
    >
      <SwiperSlide>
        Slide 1
      </SwiperSlide>
      <SwiperSlide>
        Slide 2
      </SwiperSlide>
    </Swiper>
  </>
)

Thank you so much, this acctualy help me solve my problem!!!

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone! Typescript solution

This works for me thank you

This works for me! Thank you very much, @coolzyte @chitalian

Typescript Solution without useState

// import type SwiperCore from 'swiper';
// import { Swiper, SwiperSlide } from 'swiper/react';

const swiperRef = useRef<SwiperCore>()

return (
  <Swiper
     onBeforeInit={(swiper) => {
       swiperRef.current = swiper
     }}
  >
    {.... my slides components}
    <CustomPrevButton onClick={() => swiperRef.current?.slidePrev()} />
    <CustomNextButton onClick={() => swiperRef.current?.slideNext()} />
  </Swiper>
)

simply this works for me without type error.

Don’t know if this still convenient but. I fixed this issue by import the Navigation module from swiper. and everything works as expected.

import SwiperCore, { Navigation, Pagination, Scrollbar, A11y } from 'swiper';

import { Swiper, SwiperSlide } from 'swiper/react';


const PaintingsSlider = () => {
	SwiperCore.use([Navigation]);
...

return (
    <Swiper slidesPerView={1}
        loop={true}
        navigation={{
	    nextEl: nextRef.current,
	    prevEl: prevRef.current,
	}}
....
)

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!

Typescript solution

This works for me thank you

Another way to solve this problem:

// use the `useState` hook instead of `useRef`
const [prevEl, setPrevEl] = useState<HTMLElement | null>(null)
const [nextEl, setPrevEl] = useState<HTMLElement | null>(null)
<Swiper navigation={{ prevEl, nextEl }}>
// some slides
</Swiper>
<div ref={(node) => setPrevEl(node)}>prev</div>
<div ref={(node) => setNextEl(node)}>next</div>

I’ve tried all the solutions above using Typescript and none worked, only this one worked for me.

Could anybody show me full code? (and if you could Typescript?) I’m strugging making this.

swiper.params.navigation.prevEl = prevRef.current; in onInit can be error:

TS2339: Property 'prevEl' does not exist on type 'boolean | NavigationOptions'.
Property 'prevEl' does not exist on type 'false'.

try this (swiper.params.navigation as NavigationOptions).prevEl = prevRef.current;

@kruzyk I only used Swiper once so I imagine other people might be able to inform better what is the right approach for this. I don’t know what you meant about swiper-button-disabled but see an example below if you want to disable the buttons once Swiper reached the beginning and end of the slides.

import { useState } from "react";
import { Swiper, SwiperSlide, SwiperProps, useSwiper } from "swiper/react"; // Swiper 8.3.1
import "swiper/css";

const CustomNavigation = ({ slot }: { slot: "container-end" }) => {
  const swiper = useSwiper();
  const [slideProgress, setSlideProgress] = useState<number>(0);

  swiper.on("slideChange", (e) => {
    setSlideProgress(e.progress);
  });

  return (
    <div slot={slot}>
      <button onClick={() => swiper.slidePrev()} disabled={slideProgress === 0}>
        Slide to the previous slide
      </button>
      <button onClick={() => swiper.slideNext()} disabled={slideProgress === 1}>
        Slide to the next slide
      </button>
    </div>
  );
};

export default ({ children, ...other }: SwiperProps) => {
  return (
    <Swiper {...other}>
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
      <CustomNavigation slot="container-end" />
    </Swiper>
  );
};

I know it’s not perfect solution for typescript users, but if none of previous examples works for you, maybe this one will. I know that using “any” isn’t great, but it’s still better than ts-ignore.

const Projects = () => {
  const [swipe, setSwipe] = useState<any>();

  return (
    <>
        <button onClick={() => swipe?.slidePrev()}>backward</button>
        <Swiper
          onBeforeInit={(swipper) => setSwipe(swipper)}
        >
          {projects?.map((value: IProjectCard) => {
            return (
              <SwiperSlide key={uuidv4()}>
                <ProjectCard
                  key={uuidv4()}
                  header={value.header}
                />
              </SwiperSlide>
            );
          })}
        </Swiper>
        <button onClick={() => swipe?.slideNext()}>forward</button>
    </>
  );
};

P.S. Do not downvote me, it’s my first comment on github. Just say my solution sucks. 😄

This solution worked for me. Using React, typescript and swiper@6.8.4

I know it’s not perfect solution for typescript users, but if none of previous examples works for you, maybe this one will. I know that using “any” isn’t great, but it’s still better than ts-ignore.

const Projects = () => {
  const [swipe, setSwipe] = useState<any>();

  return (
    <>
        <button onClick={() => swipe?.slidePrev()}>backward</button>
        <Swiper
          onBeforeInit={(swipper) => setSwipe(swipper)}
        >
          {projects?.map((value: IProjectCard) => {
            return (
              <SwiperSlide key={uuidv4()}>
                <ProjectCard
                  key={uuidv4()}
                  header={value.header}
                />
              </SwiperSlide>
            );
          })}
        </Swiper>
        <button onClick={() => swipe?.slideNext()}>forward</button>
    </>
  );
};

P.S. Do not downvote me, it’s my first comment on github. Just say my solution sucks. 😄

@taze-fullstack you can try this

const navigationPrevRef = useRef<HTMLDivElement>(null);
const navigationNextRef = useRef<HTMLDivElement>(null);

in swiper

        navigation={{
          prevEl: navigationPrevRef.current ? navigationPrevRef.current : undefined,
          nextEl: navigationNextRef.current ? navigationNextRef.current : undefined,
        }}

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!

Typescript solution

“swiper”: “^8.2.6”

This works for me, thanks

Another way to solve this problem:

// use the `useState` hook instead of `useRef`
const [prevEl, setPrevEl] = useState<HTMLElement | null>(null)
const [nextEl, setPrevEl] = useState<HTMLElement | null>(null)
<Swiper navigation={{ prevEl, nextEl }}>
// some slides
</Swiper>
<div ref={(node) => setPrevEl(node)}>prev</div>
<div ref={(node) => setNextEl(node)}>next</div>

This works for me

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

Type 'MutableRefObject<null> | null' is not assignable to type 'HTMLElement | CSSSelector | null | undefined'.
  Type 'MutableRefObject<null>' is not assignable to type 'HTMLElement | CSSSelector | null | undefined'.
    Type 'MutableRefObject<null>' is missing the following properties from type 'CSSSelector': charAt, charCodeAt, concat, indexOf, and 43 more.

How to solve this?

const useSwiperRef = <T extends HTMLElement>(): [
  T | undefined,
  React.Ref<T>
] => {
  const [wrapper, setWrapper] = useState<T>();
  const ref = useRef<T>(null);

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current);
    }
  }, []);

  return [wrapper, ref];
};

and then we could use it:

const [paginationEle, paginationRef] = useSwiperRef<HTMLDivElement>();

That`s how i create custom paginatipn container

`import { Swiper, SwiperSlide } from “swiper/react”; import s from “./Slider.module.css”; import { EffectFade, Pagination } from ‘swiper’; import “swiper/css”; import ‘swiper/css/pagination’; import React from “react”;

const Slider = () => { console.log(s); return ( <section className={s.slider}> <div className="wrapper"> <div className={s.sliderWrapper}> <Swiper className={s.sliderContiner} spaceBetween={0} autoHeight={true} slidesPerView={1} modules={[EffectFade, Pagination]} pagination={{ clickable: true, el : .${s.paginationEl}, bulletClass : s.paginationEl, bulletActiveClass : s.paginationBtnActive }} effect=“fade” > <SwiperSlide className={s.sliderSlide}> <div className={s.slideContainer}>

01

Our team works with our partners on all aspects of project execution from origination through disposition, providing investors with expeditious access to well-established and well-run projects that often have long track records of success.

</div> </SwiperSlide> <SwiperSlide className={s.sliderSlide}> <div className={s.slideContainer}>

02

Our team works with our partners on all aspects of project execution from origination through disposition, providing investors with expeditious access to well-established and well-run projects that often have long track records of success. Our team works with our partners on all aspects of project execution from origination through disposition, providing investors with expeditious access to well-established and well-run projects that often have long track records of success.

</div> </SwiperSlide> <SwiperSlide className={s.sliderSlide}> <div className={s.slideContainer}>

03

Our team works with our partners on all aspects of project execution from origination through disposition, providing investors with expeditious access to well-established and well-run projects that often have long track records of success.

</div> </SwiperSlide> </Swiper> <div className={s.paginationEl}></div> </div> </div> </section> ); };

export default Slider;`

and my styles:

`.paginationEl{

}

.paginationBtn{ width: 20px; height: 20px; }

.paginationBtnActive{

}`

It’s far from developer friendly, these bypass techniques should be default to swiper.js…

In case this helps anyone using Swiper with TypeScript:

import SwiperCore from 'swiper';
import {NavigationOptions} from 'swiper/types/components/navigation';
import {PaginationOptions} from 'swiper/types/components/pagination';

const CustomSwiper = () => {
  const navPrevButton = React.useRef<HTMLButtonElement>(null);
  const navNextButton = React.useRef<HTMLButtonElement>(null);
  const paginationLabel = React.useRef<HTMLHeadingElement>(null);

  const onBeforeInit = (Swiper: SwiperCore): void => {
    if (typeof Swiper.params.navigation !== 'boolean') {
      const navigation = Swiper.params.navigation;
      navigation.prevEl = navPrevButton.current;
      navigation.nextEl = navNextButton.current;
    }

    if (typeof Swiper.params.pagination !== 'boolean') {
      Swiper.params.pagination.el = paginationLabel.current;
    }
  };

 return (
    <Swiper onBeforeInit={onBeforeInit}>
      <SwiperSlide>1</SwiperSlide>
      <SwiperSlide>2</SwiperSlide>
      <SwiperSlide>3</SwiperSlide>
      <button ref={navPrevButton} />
      <button ref={navNextButton} />
    </Swiper>
  )
}

No need to define navigation/pagination props on Swiper unless you need/want to override other things.

I had to make some modifications to make it work, im new to TS so open to feedback

const onBeforeInit = (Swiper: SwiperCore): void => {
    if (typeof Swiper.params.navigation !== "boolean") {
      const navigation = Swiper.params.navigation;
      if (navigation !== undefined) {
        navigation.prevEl = swiperNavPrevRef.current;
        navigation.nextEl = swiperNavNextRef.current;
      }
    }
  };

Worked for me Thanks

I know it’s not perfect solution for typescript users, but if none of previous examples works for you, maybe this one will. I know that using “any” isn’t great, but it’s still better than ts-ignore.

const Projects = () => {
  const [swipe, setSwipe] = useState<any>();

  return (
    <>
        <button onClick={() => swipe?.slidePrev()}>backward</button>
        <Swiper
          onBeforeInit={(swipper) => setSwipe(swipper)}
        >
          {projects?.map((value: IProjectCard) => {
            return (
              <SwiperSlide key={uuidv4()}>
                <ProjectCard
                  key={uuidv4()}
                  header={value.header}
                />
              </SwiperSlide>
            );
          })}
        </Swiper>
        <button onClick={() => swipe?.slideNext()}>forward</button>
    </>
  );
};

P.S. Do not downvote me, it’s my first comment on github. Just say my solution sucks. 😄

Thanks! This is working well. Simplest cleanest solution here.

@markcnunes nice, but what about dynamic changes like adding swiper-button-disabled? I tried on 8.3.1 and this seems to not work…

As mentioned by Muskos on https://github.com/nolimits4web/swiper/issues/3855#issuecomment-10722631 it is now possible to use useSwiper to generate a custom navigation.

const Carousel = ({ children, ...other }: SwiperProps) => {
  return (
    <Swiper {...other}>
      {children}
      <CustomNavigation />
    </Swiper>
  )
}

export default Carousel

const CustomNavigation = () => {
  const swiper = useSwiper()
  return (
    <div>
      <button onClick={() => swiper.slideNext()}>
        Slide to the next slide
      </button>
      <button onClick={() => swiper.slidePrev()}>
        Slide to the previous slide
      </button>
    </div>
  )
}

And the pagination can be customized via the Swiper configuration - https://swiperjs.com/demos#pagination-custom

export default function App() {
  const pagination = {
    clickable: true,
    renderBullet: function (index, className) {
      return '<span class="' + className + '">' + (index + 1) + "</span>";
    },
  };

  return (
    <>
      <Swiper
        pagination={pagination}
        modules={[Pagination]}
        className="mySwiper"
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
        <SwiperSlide>Slide 3</SwiperSlide>
      </Swiper>
    </>
  );
}

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

Type 'MutableRefObject<null> | null' is not assignable to type 'HTMLElement | CSSSelector | null | undefined'.
  Type 'MutableRefObject<null>' is not assignable to type 'HTMLElement | CSSSelector | null | undefined'.
    Type 'MutableRefObject<null>' is missing the following properties from type 'CSSSelector': charAt, charCodeAt, concat, indexOf, and 43 more.

How to solve this?

const useSwiperRef = <T extends HTMLElement>(): [
  T | undefined,
  React.Ref<T>
] => {
  const [wrapper, setWrapper] = useState<T>();
  const ref = useRef<T>(null);

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current);
    }
  }, []);

  return [wrapper, ref];
};

and then we could use it:

const [paginationEle, paginationRef] = useSwiperRef<HTMLDivElement>();

TypeScript solution

With the previous solution, the buttons might not work. You need to tweak it a bit. Be sure to initialize the state with null:

const useSwiperRef = <T extends HTMLElement>(): [T | null, React.Ref<T>] => {
  const [wrapper, setWrapper] = useState<T | null>(null)
  const ref = useRef<T>(null)

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current)
    }
  }, [])

  return [wrapper, ref]
}
  const [nextEl, nextElRef] = useSwiperRef<HTMLButtonElement>()
  const [prevEl, prevElRef] = useSwiperRef<HTMLButtonElement>()
...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
        }}
      >
      ...
  </Swiper>
  <Button ref={prevElRef} />
  <Button ref={nextElRef} />
  ...

Work for me…

In a new version of slider you can use hook https://swiperjs.com/react#use-swiper

In case someone stumbles here:

@michaelthorne solution works for me, the solution from @ddtch looked very good in the beginning, but the navigation buttons are sometimes not triggering. Dont know why, maybe its the compination with SSR / next.js / next/image (lazy loading images in slides).

@michaelthorne Thank you for your code. I did exact same way because there’s only way to cast to any it or, just @ts-ignore.

  • Swiper Version: 6.2.0
  • Platform/Target and Browser Versions: All

Also posten on Stackoverflow: Swiper React | How to create custom navigation/pagination components using React refs?

What you did

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)

  return (
    <Swiper
      navigation={{
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}

Expected Behavior

To work

Actual Behavior

Did not work

SwiperJS documentation states that navigation prevEl/nextEl can either be of type “string” or “HTMLElement”. Using HTML nodes allows for navigation prevEl/nextEl to be scoped to each rendered instance of MySwiper. In React this is usually done with “refs”.

const App = () => (
  <div>
    <MySwiper /> // MySwiper1
    <MySwiper /> // MySwiper2
  </div>
)

In the App example above, navigation prevEl/nextEl from “MySwiper2” should not trigger sliding of “MySwiper1”, which is what would happen if one would have used string selectors like { prevEl: '.prev', nextEl: '.next' }. Obviously (if even possible within the application) one could generate unique classnames. A better solution would be to pass the HTML elements as these are already unique. Is this possible somehow with React refs?

My current hacky workaround:

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)

  return (
    <Swiper
      navigation={{
        // Both prevEl & nextEl are null at render so this does not work
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
      onSwiper={(swiper) => {
        // Delay execution for the refs to be defined
        setTimeout(() => {
          // Override prevEl & nextEl now that refs are defined
          swiper.params.navigation.prevEl = navigationPrevRef.current
          swiper.params.navigation.nextEl = navigationNextRef.current

          // Re-init navigation
          swiper.navigation.destroy()
          swiper.navigation.init()
          swiper.navigation.update()
        })
      }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}

Thanks in advance!

thank you you saved me @maeertin 🙏

Another way to solve this problem:

// use the `useState` hook instead of `useRef`
const [prevEl, setPrevEl] = useState<HTMLElement | null>(null)
const [nextEl, setPrevEl] = useState<HTMLElement | null>(null)
<Swiper navigation={{ prevEl, nextEl }}>
// some slides
</Swiper>
<div ref={(node) => setPrevEl(node)}>prev</div>
<div ref={(node) => setNextEl(node)}>next</div>

This is quite straight forward. Thanks for sharing!

The above schemes have type errors in the default typescript project created by CRA and cannot be used. The following is an example swiper@6.8.4 Workarounds available in version typescript.

import { useRef, useEffect } from "react";
import Swiper from "swiper";
import "swiper/swiper-bundle.css";

function Banner() {
  const domRef = useRef<HTMLDivElement>(null);
  const swiperRef = useRef<Swiper>();
  const ARRAY_LIST: number[] = [1, 2, 3, 4, 5, 6];

  useEffect(() => {
    const mySwiper = new Swiper(domRef.current!, {
      slidesPerView: "auto",
      loop: false,
    });
    swiperRef.current = mySwiper;
    return () => swiperRef.current!.destroy(true, true);
  }, []);

  return (
    <>
      <button onClick={() => swiperRef.current!.slidePrev()}>Prev</button>
      <div className="swiper-container" ref={domRef}>
        <ul className="swiper-wrapper">
          {ARRAY_LIST.map((item) => (
            <li className="swiper-slide" key={item}>
              {item}
            </li>
          ))}
        </ul>
      </div>
      <button onClick={() => swiperRef.current!.slideNext()}>Next</button>
    </>
  );
}

Ohh that’s pretty nice code writing. Thank you for your advise, I really appreciate it.