diff --git a/cypress/integration/modules/a11y.js b/cypress/integration/modules/a11y.js
index 5db0ea497..9ecd1df9a 100644
--- a/cypress/integration/modules/a11y.js
+++ b/cypress/integration/modules/a11y.js
@@ -14,6 +14,16 @@ context('Core', () => {
       cy.getSliderWrapper().should('have.attr', 'aria-live', 'polite');
     });
 
+    it('paginationBulletMessage', () => {
+      cy.initSwiper({
+        pagination: true,
+        a11y: { paginationBulletMessage: 'Slide to {{index}}' },
+      });
+      cy.getPaginationBullet(1).should('have.attr', 'aria-label', 'Slide to 2');
+      cy.getPaginationBullet(4).should('have.attr', 'aria-label', 'Slide to 5');
+      cy.getPaginationBullet(9).should('have.attr', 'aria-label', 'Slide to 10');
+    });
+
     it('should add aria-role-description="slide" to swiper-slide', () => {
       cy.initSwiper({
         a11y: { itemRoleDescriptionMessage: 'test' },
@@ -21,6 +31,15 @@ context('Core', () => {
       cy.getSlides().should('have.attr', 'aria-role-description', 'test');
     });
 
+    it('should add aria-label="1 of 10" to swiper-slide', () => {
+      cy.initSwiper({
+        a11y: { slideLabelMessage: '{{index}} of {{slidesLength}}' },
+      });
+      cy.getSlide(0).should('have.attr', 'aria-label', '1 of 10');
+      cy.getSlide(4).should('have.attr', 'aria-label', '5 of 10');
+      cy.getSlide(9).should('have.attr', 'aria-label', '10 of 10');
+    });
+
     it('should add aria-role-description="slide" to swiper-container', () => {
       cy.initSwiper({
         a11y: { containerRoleDescriptionMessage: 'test' },
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 7ea4d262d..618b23e75 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -30,6 +30,7 @@ Cypress.Commands.add('getSliderContainer', { prevSubject: 'optional' }, () => {
 Cypress.Commands.add('getSlide', { prevSubject: 'optional' }, (subject, slideIndex) => {
   return cy.get(`.swiper-slide:nth-child(${slideIndex + 1})`);
 });
+
 Cypress.Commands.add('getSlideContains', { prevSubject: 'optional' }, (subject, content) => {
   cy.get('.swiper-container').contains(content);
 });
@@ -40,6 +41,10 @@ Cypress.Commands.add('swiperPage', { prevSubject: 'optional' }, () => {
   return cy.visit('cypress/test.html');
 });
 
+Cypress.Commands.add('getPaginationBullet', { prevSubject: 'optional' }, (subject, bulletIndex) => {
+  return cy.get(`.swiper-pagination-bullet:nth-child(${bulletIndex + 1})`);
+});
+
 Cypress.Commands.add(
   'initSwiper',
   { prevSubject: 'optional' },
diff --git a/playground/angular/src/app/app.component.html b/playground/angular/src/app/app.component.html
index a09ec7389..2e09eaccd 100644
--- a/playground/angular/src/app/app.component.html
+++ b/playground/angular/src/app/app.component.html
@@ -5,7 +5,7 @@
       [slidesPerView]="3"
       [spaceBetween]="50"
       [pagination]="{ type: 'fraction' }"
-      virtual
+      [virtual]="true"
       slideActiveClass="swiper-active whyWouldIuseCustomClass"
       [centeredSlides]="true"
       navigation
@@ -16,6 +16,22 @@
       <ng-template swiperSlide>Slide</ng-template>
       <ng-template swiperSlide>Slide</ng-template>
     </swiper>
+
+    <swiper
+      #swiperVirtualRef
+      [slidesPerView]="3"
+      [spaceBetween]="50"
+      [pagination]="{ type: 'fraction' }"
+      [virtual]="true"
+      [centeredSlides]="true"
+      navigation
+    >
+      <ng-template swiperSlide *ngFor="let slide of slides$ | async; index as i"
+        >Slide {{ slide }}</ng-template
+      >
+    </swiper>
+    <button (click)="getSlides()">Get slides</button>
+
     <swiper [zoom]="true" [autoplay]="true">
       <ng-template swiperSlide class="custom-class" [zoom]="true">
         <img src="https://swiperjs.com/demos/images/nature-1.jpg" />
@@ -73,87 +89,89 @@
     <button (click)="exampleConfig = { slidesPerView: 2 }">changeConfig</button>
 
     {{ exampleConfig | json }}
+  </div>
+  <div>
     <swiper
       #swiper
       [slidesPerView]="1"
       [centeredSlides]="true"
-      [navigation]="{ prevEl: '.swiper-navigation-prev', nextEl: '.swiper-navigation-next' }"
+      [navigation]="{ prevEl: prevEl, nextEl: nextEl }"
       [pagination]="pagination"
     >
       <ng-template swiperSlide *ngFor="let slide of slides2; index as i">
         {{ i }} - {{ slide }}
       </ng-template>
     </swiper>
-    <button type="button" class="swiper-navigation-prev">&lt;</button>
-    <button type="button" class="swiper-navigation-next">&gt;</button>
+    <button type="button" #nextEl class="swiper-navigation-prev">&lt;</button>
+    <button type="button" #prevEl class="swiper-navigation-next">&gt;</button>
     <hr />
     <button (click)="replaceSlides()">replace slides</button>
     <button (click)="togglePagination()">Toggle pagination</button>
   </div>
   <div>
-  <swiper
-    [slidesPerView]="1"
-    [spaceBetween]="50"
-    [navigation]="true"
-    [pagination]="{ clickable: true }"
-    [thumbs]="{ swiper: thumbsSwiper }"
-  >
-    <ng-template swiperSlide>Slide 1</ng-template>
-    <ng-template swiperSlide>Slide 2</ng-template>
-    <ng-template swiperSlide>Slide 3</ng-template>
-    <ng-template swiperSlide>Slide 4</ng-template>
-    <ng-template swiperSlide>Slide 5</ng-template>
-    <ng-template swiperSlide>Slide 6</ng-template>
-  </swiper>
-  <swiper
-    [slidesPerView]="3"
-    [spaceBetween]="50"
-    (swiper)="setThumbsSwiper($event)"
-    [navigation]="{}"
-    [pagination]="{ clickable: true }"
-    [scrollbar]="{ draggable: true }"
-    [watchSlidesVisibility]="true"
-    [watchSlidesProgress]="true"
-  >
-    <ng-template swiperSlide>Slide 1</ng-template>
-    <ng-template swiperSlide>Slide 2</ng-template>
-    <ng-template swiperSlide>Slide 3</ng-template>
-    <ng-template swiperSlide>Slide 4</ng-template>
-    <ng-template swiperSlide>Slide 5</ng-template>
-    <ng-template swiperSlide>Slide 6</ng-template>
-  </swiper>
-</div>
-<div>
-<swiper
-  [slidesPerView]="1"
-  [spaceBetween]="50"
-  [navigation]="true"
-  [pagination]="{ clickable: true }"
-  [controller]="{ control: controlledSwiper }"
->
-  <ng-template swiperSlide>Slide 1</ng-template>
-  <ng-template swiperSlide>Slide 2</ng-template>
-  <ng-template swiperSlide>Slide 3</ng-template>
-  <ng-template swiperSlide>Slide 4</ng-template>
-  <ng-template swiperSlide>Slide 5</ng-template>
-  <ng-template swiperSlide>Slide 6</ng-template>
-</swiper>
-<swiper
-  [slidesPerView]="3"
-  [spaceBetween]="50"
-  (swiper)="setControlledSwiper($event)"
-  [navigation]="{}"
-  [pagination]="{ clickable: true }"
-  [scrollbar]="{ draggable: true }"
-  [watchSlidesVisibility]="true"
-  [watchSlidesProgress]="true"
->
-  <ng-template swiperSlide>Slide 1</ng-template>
-  <ng-template swiperSlide>Slide 2</ng-template>
-  <ng-template swiperSlide>Slide 3</ng-template>
-  <ng-template swiperSlide>Slide 4</ng-template>
-  <ng-template swiperSlide>Slide 5</ng-template>
-  <ng-template swiperSlide>Slide 6</ng-template>
-</swiper>
-</div>
+    <swiper
+      [slidesPerView]="1"
+      [spaceBetween]="50"
+      [navigation]="true"
+      [pagination]="{ clickable: true }"
+      [thumbs]="{ swiper: thumbsSwiper }"
+    >
+      <ng-template swiperSlide>Slide 1</ng-template>
+      <ng-template swiperSlide>Slide 2</ng-template>
+      <ng-template swiperSlide>Slide 3</ng-template>
+      <ng-template swiperSlide>Slide 4</ng-template>
+      <ng-template swiperSlide>Slide 5</ng-template>
+      <ng-template swiperSlide>Slide 6</ng-template>
+    </swiper>
+    <swiper
+      [slidesPerView]="3"
+      [spaceBetween]="50"
+      (swiper)="setThumbsSwiper($event)"
+      [navigation]="{}"
+      [pagination]="{ clickable: true }"
+      [scrollbar]="{ draggable: true }"
+      [watchSlidesVisibility]="true"
+      [watchSlidesProgress]="true"
+    >
+      <ng-template swiperSlide>Slide 1</ng-template>
+      <ng-template swiperSlide>Slide 2</ng-template>
+      <ng-template swiperSlide>Slide 3</ng-template>
+      <ng-template swiperSlide>Slide 4</ng-template>
+      <ng-template swiperSlide>Slide 5</ng-template>
+      <ng-template swiperSlide>Slide 6</ng-template>
+    </swiper>
+  </div>
+  <div>
+    <swiper
+      [slidesPerView]="1"
+      [spaceBetween]="50"
+      [navigation]="true"
+      [pagination]="{ clickable: true }"
+      [controller]="{ control: controlledSwiper }"
+    >
+      <ng-template swiperSlide>Slide 1</ng-template>
+      <ng-template swiperSlide>Slide 2</ng-template>
+      <ng-template swiperSlide>Slide 3</ng-template>
+      <ng-template swiperSlide>Slide 4</ng-template>
+      <ng-template swiperSlide>Slide 5</ng-template>
+      <ng-template swiperSlide>Slide 6</ng-template>
+    </swiper>
+    <swiper
+      [slidesPerView]="3"
+      [spaceBetween]="50"
+      (swiper)="setControlledSwiper($event)"
+      [navigation]="{}"
+      [pagination]="{ clickable: true }"
+      [scrollbar]="{ draggable: true }"
+      [watchSlidesVisibility]="true"
+      [watchSlidesProgress]="true"
+    >
+      <ng-template swiperSlide>Slide 1</ng-template>
+      <ng-template swiperSlide>Slide 2</ng-template>
+      <ng-template swiperSlide>Slide 3</ng-template>
+      <ng-template swiperSlide>Slide 4</ng-template>
+      <ng-template swiperSlide>Slide 5</ng-template>
+      <ng-template swiperSlide>Slide 6</ng-template>
+    </swiper>
+  </div>
 </main>
diff --git a/playground/angular/src/app/app.component.ts b/playground/angular/src/app/app.component.ts
index 749f33cea..43e473bda 100644
--- a/playground/angular/src/app/app.component.ts
+++ b/playground/angular/src/app/app.component.ts
@@ -1,4 +1,5 @@
 import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
 import { SwiperComponent } from 'src/angular/src/public-api';
 import SwiperCore, {
   Navigation,
@@ -34,9 +35,14 @@ export class AppComponent {
 
   show: boolean;
   thumbs: any;
+  slides$ = new BehaviorSubject<string[]>(['']);
   constructor(private cd: ChangeDetectorRef) {}
   ngOnInit() {}
 
+  getSlides() {
+    this.slides$.next(Array.from({ length: 600 }).map((el, index) => `Slide ${index + 1}`));
+  }
+
   thumbsSwiper: any;
   setThumbsSwiper(swiper) {
     this.thumbsSwiper = swiper;
diff --git a/src/angular/src/swiper.component.ts b/src/angular/src/swiper.component.ts
index 1c666883e..bc821162d 100644
--- a/src/angular/src/swiper.component.ts
+++ b/src/angular/src/swiper.component.ts
@@ -164,7 +164,10 @@ export class SwiperComponent implements OnInit {
     });
     if (
       typeof this._navigation !== 'boolean' &&
-      (typeof this._navigation?.nextEl === 'string' || typeof this._navigation?.prevEl === 'string')
+      (typeof this._navigation?.nextEl === 'string' ||
+        typeof this._navigation?.prevEl === 'string' ||
+        typeof this._navigation?.nextEl === 'object' ||
+        typeof this._navigation?.prevEl === 'object')
     ) {
       this.showNavigation = false;
     }
@@ -181,7 +184,10 @@ export class SwiperComponent implements OnInit {
     this._pagination = setProperty(val, {
       el: current || null,
     });
-    if (typeof this._pagination !== 'boolean' && typeof this._pagination?.el === 'string') {
+    if (
+      typeof this._pagination !== 'boolean' &&
+      (typeof this._pagination?.el === 'string' || typeof this._pagination?.el === 'object')
+    ) {
       this.showPagination = false;
     }
   }
@@ -197,7 +203,10 @@ export class SwiperComponent implements OnInit {
     this._scrollbar = setProperty(val, {
       el: current || null,
     });
-    if (typeof this._scrollbar !== 'boolean' && typeof this._scrollbar?.el === 'string') {
+    if (
+      typeof this._scrollbar !== 'boolean' &&
+      (typeof this._scrollbar?.el === 'string' || typeof this._scrollbar?.el === 'object')
+    ) {
       this.showScrollbar = false;
     }
   }
@@ -471,6 +480,11 @@ export class SwiperComponent implements OnInit {
     if (!this.virtual) {
       this.prependSlides = of(this.slides.slice(this.slides.length - this.loopedSlides));
       this.appendSlides = of(this.slides.slice(0, this.loopedSlides));
+    } else if (this.swiperRef && this.swiperRef.virtual) {
+      this._ngZone.runOutsideAngular(() => {
+        this.swiperRef.virtual.slides = this.slides;
+        this.swiperRef.virtual.update(true);
+      });
     }
     this._changeDetectorRef.detectChanges();
   };
diff --git a/src/angular/src/utils/utils.ts b/src/angular/src/utils/utils.ts
index 7f31f4096..5f2452fd4 100644
--- a/src/angular/src/utils/utils.ts
+++ b/src/angular/src/utils/utils.ts
@@ -1,5 +1,10 @@
 export function isObject(o) {
-  return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object;
+  return (
+    typeof o === 'object' &&
+    o !== null &&
+    o.constructor &&
+    Object.prototype.toString.call(o).slice(8, -1) === 'Object'
+  );
 }
 
 export function extend(target, src) {
diff --git a/src/components/a11y/a11y.js b/src/components/a11y/a11y.js
index 801bdc0ae..8833cf3f3 100644
--- a/src/components/a11y/a11y.js
+++ b/src/components/a11y/a11y.js
@@ -167,7 +167,10 @@ const A11y = {
     swiper.a11y.addElRole($(swiper.slides), 'group');
     swiper.slides.each((slideEl) => {
       const $slideEl = $(slideEl);
-      swiper.a11y.addElLabel($slideEl, `${$slideEl.index() + 1} / ${swiper.slides.length}`);
+      const ariaLabelMessage = params.slideLabelMessage
+        .replace(/\{\{index\}\}/, $slideEl.index() + 1)
+        .replace(/\{\{slidesLength\}\}/, swiper.slides.length);
+      swiper.a11y.addElLabel($slideEl, ariaLabelMessage);
     });
 
     // Navigation
@@ -259,6 +262,7 @@ export default {
       firstSlideMessage: 'This is the first slide',
       lastSlideMessage: 'This is the last slide',
       paginationBulletMessage: 'Go to slide {{index}}',
+      slideLabelMessage: '{{index}} / {{slidesLength}}',
       containerMessage: null,
       containerRoleDescriptionMessage: null,
       itemRoleDescriptionMessage: null,
diff --git a/src/components/core/core-class.js b/src/components/core/core-class.js
index 1e5692163..f8f22b80d 100644
--- a/src/components/core/core-class.js
+++ b/src/components/core/core-class.js
@@ -49,7 +49,11 @@ class Swiper {
   constructor(...args) {
     let el;
     let params;
-    if (args.length === 1 && args[0].constructor && args[0].constructor === Object) {
+    if (
+      args.length === 1 &&
+      args[0].constructor &&
+      Object.prototype.toString.call(args[0]).slice(8, -1) === 'Object'
+    ) {
       params = args[0];
     } else {
       [el, params] = args;
diff --git a/src/components/core/update/updateSlides.js b/src/components/core/update/updateSlides.js
index 18190feff..bf5b35c02 100644
--- a/src/components/core/update/updateSlides.js
+++ b/src/components/core/update/updateSlides.js
@@ -167,7 +167,7 @@ export default function updateSlides() {
         const paddingRight = getDirectionPropertyValue(slideStyles, 'padding-right');
         const marginLeft = getDirectionPropertyValue(slideStyles, 'margin-left');
         const marginRight = getDirectionPropertyValue(slideStyles, 'margin-right');
-        const boxSizing = slideStyles.getPropertyValue(slideStyles, 'box-sizing');
+        const boxSizing = slideStyles.getPropertyValue('box-sizing');
         if (boxSizing && boxSizing === 'border-box') {
           slideSize = width + marginLeft + marginRight;
         } else {
diff --git a/src/components/keyboard/keyboard.js b/src/components/keyboard/keyboard.js
index 94df48a60..9637a6894 100644
--- a/src/components/keyboard/keyboard.js
+++ b/src/components/keyboard/keyboard.js
@@ -56,15 +56,19 @@ const Keyboard = {
       ) {
         return undefined;
       }
+
+      const $el = swiper.$el;
+      const swiperWidth = $el[0].clientWidth;
+      const swiperHeight = $el[0].clientHeight;
       const windowWidth = window.innerWidth;
       const windowHeight = window.innerHeight;
       const swiperOffset = swiper.$el.offset();
       if (rtl) swiperOffset.left -= swiper.$el[0].scrollLeft;
       const swiperCoord = [
         [swiperOffset.left, swiperOffset.top],
-        [swiperOffset.left + swiper.width, swiperOffset.top],
-        [swiperOffset.left, swiperOffset.top + swiper.height],
-        [swiperOffset.left + swiper.width, swiperOffset.top + swiper.height],
+        [swiperOffset.left + swiperWidth, swiperOffset.top],
+        [swiperOffset.left, swiperOffset.top + swiperHeight],
+        [swiperOffset.left + swiperWidth, swiperOffset.top + swiperHeight],
       ];
       for (let i = 0; i < swiperCoord.length; i += 1) {
         const point = swiperCoord[i];
diff --git a/src/react/utils.js b/src/react/utils.js
index 7993804ff..0378af8df 100644
--- a/src/react/utils.js
+++ b/src/react/utils.js
@@ -1,5 +1,10 @@
 function isObject(o) {
-  return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object;
+  return (
+    typeof o === 'object' &&
+    o !== null &&
+    o.constructor &&
+    Object.prototype.toString.call(o).slice(8, -1) === 'Object'
+  );
 }
 
 function extend(target, src) {
diff --git a/src/svelte/utils.js b/src/svelte/utils.js
index 7993804ff..0378af8df 100644
--- a/src/svelte/utils.js
+++ b/src/svelte/utils.js
@@ -1,5 +1,10 @@
 function isObject(o) {
-  return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object;
+  return (
+    typeof o === 'object' &&
+    o !== null &&
+    o.constructor &&
+    Object.prototype.toString.call(o).slice(8, -1) === 'Object'
+  );
 }
 
 function extend(target, src) {
diff --git a/src/types/components/a11y.d.ts b/src/types/components/a11y.d.ts
index c73cabde3..d85f3dfc4 100644
--- a/src/types/components/a11y.d.ts
+++ b/src/types/components/a11y.d.ts
@@ -72,4 +72,11 @@ export interface A11yOptions {
    * @default null
    */
   itemRoleDescriptionMessage?: string | null;
+
+  /**
+   * Message for screen readers describing the label of slide element
+   *
+   * @default '{{index}} / {{slidesLength}}'
+   */
+  slideLabelMessage?: string;
 }
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 4134f737f..c85924d0e 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -85,7 +85,12 @@ function getTranslate(el, axis = 'x') {
   return curTransform || 0;
 }
 function isObject(o) {
-  return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object;
+  return (
+    typeof o === 'object' &&
+    o !== null &&
+    o.constructor &&
+    Object.prototype.toString.call(o).slice(8, -1) === 'Object'
+  );
 }
 function extend(...args) {
   const to = Object(args[0]);
diff --git a/src/vue/utils.js b/src/vue/utils.js
index e2e895d0c..a6839f045 100644
--- a/src/vue/utils.js
+++ b/src/vue/utils.js
@@ -1,5 +1,10 @@
 function isObject(o) {
-  return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object;
+  return (
+    typeof o === 'object' &&
+    o !== null &&
+    o.constructor &&
+    Object.prototype.toString.call(o).slice(8, -1) === 'Object'
+  );
 }
 
 function extend(target, src) {