diff --git a/js/src/carousel.js b/js/src/carousel.js
index a5451538cde511603f74e8e96a1431eae5abb49f..93f896e535c1502804aa8f2dd1291fca7837af0f 100644
--- a/js/src/carousel.js
+++ b/js/src/carousel.js
@@ -33,8 +33,8 @@ const DATA_KEY = 'bs.carousel'
 const EVENT_KEY = `.${DATA_KEY}`
 const DATA_API_KEY = '.data-api'
 
-const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key
-const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key
+const ARROW_LEFT_KEY = 'ArrowLeft'
+const ARROW_RIGHT_KEY = 'ArrowRight'
 const TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch
 const SWIPE_THRESHOLD = 40
 
@@ -342,12 +342,12 @@ class Carousel {
       return
     }
 
-    switch (event.which) {
-      case ARROW_LEFT_KEYCODE:
+    switch (event.key) {
+      case ARROW_LEFT_KEY:
         event.preventDefault()
         this.prev()
         break
-      case ARROW_RIGHT_KEYCODE:
+      case ARROW_RIGHT_KEY:
         event.preventDefault()
         this.next()
         break
diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js
index 7fdeaa4f8e9010648564ead59df78c79339de6cc..e43edd6ee2c84585ebb9962247b5efee3d5f12f6 100644
--- a/js/src/dom/event-handler.js
+++ b/js/src/dom/event-handler.js
@@ -17,7 +17,6 @@ import { defaultPreventedPreservedOnDispatch } from './polyfill'
 const $ = getjQuery()
 const namespaceRegex = /[^.]*(?=\..*)\.|.*/
 const stripNameRegex = /\..*/
-const keyEventRegex = /^key/
 const stripUidRegex = /::\d+$/
 const eventRegistry = {} // Events storage
 let uidEvent = 1
@@ -94,11 +93,6 @@ function getEvent(element) {
 }
 
 function fixEvent(event, element) {
-  // Add which for key events
-  if (event.which === null && keyEventRegex.test(event.type)) {
-    event.which = event.charCode === null ? event.keyCode : event.charCode
-  }
-
   event.delegateTarget = element
 }
 
diff --git a/js/src/dropdown.js b/js/src/dropdown.js
index aab1d6bd2a20feacd9cc229658bef43a938f8a03..b1aa6d8496a78c9136b4dd2859f02e6c041d0ef3 100644
--- a/js/src/dropdown.js
+++ b/js/src/dropdown.js
@@ -31,14 +31,14 @@ const DATA_KEY = 'bs.dropdown'
 const EVENT_KEY = `.${DATA_KEY}`
 const DATA_API_KEY = '.data-api'
 
-const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
-const SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key
-const TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key
-const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key
-const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key
-const RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)
+const ESCAPE_KEY = 'Escape'
+const SPACE_KEY = 'Space'
+const TAB_KEY = 'Tab'
+const ARROW_UP_KEY = 'ArrowUp'
+const ARROW_DOWN_KEY = 'ArrowDown'
+const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button
 
-const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)
+const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY}`)
 
 const EVENT_HIDE = `hide${EVENT_KEY}`
 const EVENT_HIDDEN = `hidden${EVENT_KEY}`
@@ -372,8 +372,8 @@ class Dropdown {
   }
 
   static clearMenus(event) {
-    if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||
-      (event.type === 'keyup' && event.which !== TAB_KEYCODE))) {
+    if (event && (event.button === RIGHT_MOUSE_BUTTON ||
+      (event.type === 'keyup' && event.key !== TAB_KEY))) {
       return
     }
 
@@ -401,7 +401,7 @@ class Dropdown {
 
       if (event && ((event.type === 'click' &&
           /input|textarea/i.test(event.target.tagName)) ||
-          (event.type === 'keyup' && event.which === TAB_KEYCODE)) &&
+          (event.type === 'keyup' && event.key === TAB_KEY)) &&
           dropdownMenu.contains(event.target)) {
         continue
       }
@@ -443,10 +443,10 @@ class Dropdown {
     //    - If key is not up or down => not a dropdown command
     //    - If trigger inside the menu => not a dropdown command
     if (/input|textarea/i.test(event.target.tagName) ?
-      event.which === SPACE_KEYCODE || (event.which !== ESCAPE_KEYCODE &&
-      ((event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE) ||
+      event.key === SPACE_KEY || (event.key !== ESCAPE_KEY &&
+      ((event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY) ||
         SelectorEngine.closest(event.target, SELECTOR_MENU))) :
-      !REGEXP_KEYDOWN.test(event.which)) {
+      !REGEXP_KEYDOWN.test(event.key)) {
       return
     }
 
@@ -460,14 +460,14 @@ class Dropdown {
     const parent = Dropdown.getParentFromElement(this)
     const isActive = this.classList.contains(CLASS_NAME_SHOW)
 
-    if (event.which === ESCAPE_KEYCODE) {
+    if (event.key === ESCAPE_KEY) {
       const button = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0]
       button.focus()
       Dropdown.clearMenus()
       return
     }
 
-    if (!isActive || event.which === SPACE_KEYCODE) {
+    if (!isActive || event.key === SPACE_KEY) {
       Dropdown.clearMenus()
       return
     }
@@ -481,11 +481,11 @@ class Dropdown {
 
     let index = items.indexOf(event.target) || 0
 
-    if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up
+    if (event.key === ARROW_UP_KEY && index > 0) { // Up
       index--
     }
 
-    if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down
+    if (event.key === ARROW_DOWN_KEY && index < items.length - 1) { // Down
       index++
     }
 
diff --git a/js/src/modal.js b/js/src/modal.js
index d5f1c8c9ae99042142c38ad3b5e177e2f761778b..0daa428a8dfeeb5c021cb4ecf46f0c713701aa9e 100644
--- a/js/src/modal.js
+++ b/js/src/modal.js
@@ -31,7 +31,7 @@ const VERSION = '4.3.1'
 const DATA_KEY = 'bs.modal'
 const EVENT_KEY = `.${DATA_KEY}`
 const DATA_API_KEY = '.data-api'
-const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key
+const ESCAPE_KEY = 'Escape'
 
 const Default = {
   backdrop: true,
@@ -299,10 +299,10 @@ class Modal {
   _setEscapeEvent() {
     if (this._isShown) {
       EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
-        if (this._config.keyboard && event.which === ESCAPE_KEYCODE) {
+        if (this._config.keyboard && event.key === ESCAPE_KEY) {
           event.preventDefault()
           this.hide()
-        } else if (!this._config.keyboard && event.which === ESCAPE_KEYCODE) {
+        } else if (!this._config.keyboard && event.key === ESCAPE_KEY) {
           this._triggerBackdropTransition()
         }
       })
diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js
index be32d19d9b36412495efa66057cbebc2a47c453a..46659a032aa9fc2ca1465156cd8f6c79d0155be0 100644
--- a/js/tests/unit/carousel.spec.js
+++ b/js/tests/unit/carousel.spec.js
@@ -70,10 +70,10 @@ describe('Carousel', () => {
         done()
       })
 
-      const keyDown = createEvent('keydown')
-      keyDown.which = 39
+      const keydown = createEvent('keydown')
+      keydown.key = 'ArrowRight'
 
-      carouselEl.dispatchEvent(keyDown)
+      carouselEl.dispatchEvent(keydown)
     })
 
     it('should go to previous item if left arrow key is pressed', done => {
@@ -100,10 +100,10 @@ describe('Carousel', () => {
         done()
       })
 
-      const keyDown = createEvent('keydown')
-      keyDown.which = 37
+      const keydown = createEvent('keydown')
+      keydown.key = 'ArrowLeft'
 
-      carouselEl.dispatchEvent(keyDown)
+      carouselEl.dispatchEvent(keydown)
     })
 
     it('should not prevent keydown if key is not ARROW_LEFT or ARROW_RIGHT', done => {
@@ -130,10 +130,10 @@ describe('Carousel', () => {
         done()
       })
 
-      const keyDown = createEvent('keydown')
-      keyDown.which = 40
+      const keydown = createEvent('keydown')
+      keydown.key = 'ArrowDown'
 
-      carouselEl.dispatchEvent(keyDown)
+      carouselEl.dispatchEvent(keydown)
     })
 
     it('should ignore keyboard events within <input>s and <textarea>s', () => {
@@ -157,34 +157,34 @@ describe('Carousel', () => {
         keyboard: true
       })
 
-      const spyKeyDown = spyOn(carousel, '_keydown').and.callThrough()
+      const spyKeydown = spyOn(carousel, '_keydown').and.callThrough()
       const spyPrev = spyOn(carousel, 'prev')
       const spyNext = spyOn(carousel, 'next')
 
-      const keyDown = createEvent('keydown', { bubbles: true, cancelable: true })
-      keyDown.which = 39
-      Object.defineProperty(keyDown, 'target', {
+      const keydown = createEvent('keydown', { bubbles: true, cancelable: true })
+      keydown.key = 'ArrowRight'
+      Object.defineProperty(keydown, 'target', {
         value: input,
         writable: true,
         configurable: true
       })
 
-      input.dispatchEvent(keyDown)
+      input.dispatchEvent(keydown)
 
-      expect(spyKeyDown).toHaveBeenCalled()
+      expect(spyKeydown).toHaveBeenCalled()
       expect(spyPrev).not.toHaveBeenCalled()
       expect(spyNext).not.toHaveBeenCalled()
 
-      spyKeyDown.calls.reset()
+      spyKeydown.calls.reset()
       spyPrev.calls.reset()
       spyNext.calls.reset()
 
-      Object.defineProperty(keyDown, 'target', {
+      Object.defineProperty(keydown, 'target', {
         value: textarea
       })
-      textarea.dispatchEvent(keyDown)
+      textarea.dispatchEvent(keydown)
 
-      expect(spyKeyDown).toHaveBeenCalled()
+      expect(spyKeydown).toHaveBeenCalled()
       expect(spyPrev).not.toHaveBeenCalled()
       expect(spyNext).not.toHaveBeenCalled()
     })
diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js
index e8a7e66ba9ee5e0b29221ff660e205ad5ffd219c..b2820f72e05e2355b4b51225a27a6490fce944d7 100644
--- a/js/tests/unit/dropdown.spec.js
+++ b/js/tests/unit/dropdown.spec.js
@@ -1068,10 +1068,10 @@ describe('Dropdown', () => {
       dropdownEl.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
 
-        const keyUp = createEvent('keyup')
+        const keyup = createEvent('keyup')
 
-        keyUp.which = 9 // Tab
-        document.dispatchEvent(keyUp)
+        keyup.key = 'Tab'
+        document.dispatchEvent(keyup)
       })
 
       dropdownEl.addEventListener('hidden.bs.dropdown', () => {
@@ -1165,10 +1165,10 @@ describe('Dropdown', () => {
         expect(first.classList.contains('show')).toEqual(true, '"show" class added on click')
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
 
-        const keyUp = createEvent('keyup')
-        keyUp.which = 9 // Tab
+        const keyup = createEvent('keyup')
+        keyup.key = 'Tab'
 
-        document.dispatchEvent(keyUp)
+        document.dispatchEvent(keyup)
       })
 
       dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => {
@@ -1180,10 +1180,10 @@ describe('Dropdown', () => {
         expect(last.classList.contains('show')).toEqual(true, '"show" class added on click')
         expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
 
-        const keyUp = createEvent('keyup')
-        keyUp.which = 9 // Tab
+        const keyup = createEvent('keyup')
+        keyup.key = 'Tab'
 
-        document.dispatchEvent(keyUp)
+        document.dispatchEvent(keyup)
       })
 
       btnGroup.addEventListener('hidden.bs.dropdown', () => {
@@ -1217,10 +1217,10 @@ describe('Dropdown', () => {
       })
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        const keyDown = createEvent('keydown')
+        const keydown = createEvent('keydown')
 
-        keyDown.which = 27
-        triggerDropdown.dispatchEvent(keyDown)
+        keydown.key = 'Escape'
+        triggerDropdown.dispatchEvent(keydown)
       })
 
       triggerDropdown.click()
@@ -1245,15 +1245,15 @@ describe('Dropdown', () => {
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
         input.focus()
-        const keyDown = createEvent('keydown')
+        const keydown = createEvent('keydown')
 
-        keyDown.which = 38
-        input.dispatchEvent(keyDown)
+        keydown.key = 'ArrowUp'
+        input.dispatchEvent(keydown)
 
         expect(document.activeElement).toEqual(input, 'input still focused')
 
         textarea.focus()
-        textarea.dispatchEvent(keyDown)
+        textarea.dispatchEvent(keydown)
 
         expect(document.activeElement).toEqual(textarea, 'textarea still focused')
         done()
@@ -1278,11 +1278,11 @@ describe('Dropdown', () => {
       const dropdown = fixtureEl.querySelector('.dropdown')
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        const keyDown = createEvent('keydown')
-        keyDown.which = 40
+        const keydown = createEvent('keydown')
+        keydown.key = 'ArrowDown'
 
-        triggerDropdown.dispatchEvent(keyDown)
-        triggerDropdown.dispatchEvent(keyDown)
+        triggerDropdown.dispatchEvent(keydown)
+        triggerDropdown.dispatchEvent(keydown)
 
         expect(document.activeElement.classList.contains('disabled')).toEqual(false, '.disabled not focused')
         expect(document.activeElement.hasAttribute('disabled')).toEqual(false, ':disabled not focused')
@@ -1314,10 +1314,10 @@ describe('Dropdown', () => {
       const dropdown = fixtureEl.querySelector('.dropdown')
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        const keyDown = createEvent('keydown')
-        keyDown.which = 40
+        const keydown = createEvent('keydown')
+        keydown.key = 'ArrowDown'
 
-        triggerDropdown.dispatchEvent(keyDown)
+        triggerDropdown.dispatchEvent(keydown)
 
         expect(document.activeElement.classList.contains('d-none')).toEqual(false, '.d-none not focused')
         expect(document.activeElement.style.display === 'none').toEqual(false, '"display: none" not focused')
@@ -1346,19 +1346,19 @@ describe('Dropdown', () => {
       const item2 = fixtureEl.querySelector('#item2')
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        const keyDown40 = createEvent('keydown')
-        keyDown40.which = 40
+        const keydownArrowDown = createEvent('keydown')
+        keydownArrowDown.key = 'ArrowDown'
 
-        triggerDropdown.dispatchEvent(keyDown40)
+        triggerDropdown.dispatchEvent(keydownArrowDown)
         expect(document.activeElement).toEqual(item1, 'item1 is focused')
 
-        document.activeElement.dispatchEvent(keyDown40)
+        document.activeElement.dispatchEvent(keydownArrowDown)
         expect(document.activeElement).toEqual(item2, 'item2 is focused')
 
-        const keyDown38 = createEvent('keydown')
-        keyDown38.which = 38
+        const keydownArrowUp = createEvent('keydown')
+        keydownArrowUp.key = 'ArrowUp'
 
-        document.activeElement.dispatchEvent(keyDown38)
+        document.activeElement.dispatchEvent(keydownArrowUp)
         expect(document.activeElement).toEqual(item1, 'item1 is focused')
 
         done()
@@ -1438,59 +1438,55 @@ describe('Dropdown', () => {
       const input = fixtureEl.querySelector('input')
       const textarea = fixtureEl.querySelector('textarea')
 
-      // Space key
-      const keyDownSpace = createEvent('keydown')
-      keyDownSpace.which = 32
+      const keydownSpace = createEvent('keydown')
+      keydownSpace.key = 'Space'
 
-      // Key up
-      const keyDownUp = createEvent('keydown')
-      keyDownSpace.which = 38
+      const keydownArrowUp = createEvent('keydown')
+      keydownArrowUp.key = 'ArrowUp'
 
-      // Key down
-      const keyDown = createEvent('keydown')
-      keyDownSpace.which = 40
+      const keydownArrowDown = createEvent('keydown')
+      keydownArrowDown.key = 'ArrowDown'
 
-      // Key escape
-      const keyDownEscape = createEvent('keydown')
-      keyDownEscape.which = 27
+      const keydownEscape = createEvent('keydown')
+      keydownEscape.key = 'Escape'
 
       dropdown.addEventListener('shown.bs.dropdown', () => {
-        // Space key
+        // Key Space
         input.focus()
-        input.dispatchEvent(keyDownSpace)
+        input.dispatchEvent(keydownSpace)
 
         expect(document.activeElement).toEqual(input, 'input still focused')
 
         textarea.focus()
-        textarea.dispatchEvent(keyDownSpace)
+        textarea.dispatchEvent(keydownSpace)
 
         expect(document.activeElement).toEqual(textarea, 'textarea still focused')
 
-        // Key up
+        // Key ArrowUp
         input.focus()
-        input.dispatchEvent(keyDownUp)
+        input.dispatchEvent(keydownArrowUp)
 
         expect(document.activeElement).toEqual(input, 'input still focused')
 
         textarea.focus()
-        textarea.dispatchEvent(keyDownUp)
+        textarea.dispatchEvent(keydownArrowUp)
 
         expect(document.activeElement).toEqual(textarea, 'textarea still focused')
 
-        // Key down
+        // Key ArrowDown
         input.focus()
-        input.dispatchEvent(keyDown)
+        input.dispatchEvent(keydownArrowDown)
 
         expect(document.activeElement).toEqual(input, 'input still focused')
 
         textarea.focus()
-        textarea.dispatchEvent(keyDown)
+        textarea.dispatchEvent(keydownArrowDown)
 
         expect(document.activeElement).toEqual(textarea, 'textarea still focused')
 
-        // Key escape
+        // Key Escape
         input.focus()
-        input.dispatchEvent(keyDownEscape)
+        input.dispatchEvent(keydownEscape)
 
         expect(triggerDropdown.classList.contains('show')).toEqual(false, 'dropdown menu is not shown')
         done()
@@ -1523,9 +1519,9 @@ describe('Dropdown', () => {
       // Key escape
       button.focus()
       // Key escape
-      const keyDownEscape = createEvent('keydown')
-      keyDownEscape.which = 27
-      button.dispatchEvent(keyDownEscape)
+      const keydownEscape = createEvent('keydown')
+      keydownEscape.key = 'Escape'
+      button.dispatchEvent(keydownEscape)
 
       setTimeout(() => {
         expect(dropdown.toggle).not.toHaveBeenCalled()
diff --git a/js/tests/unit/modal.spec.js b/js/tests/unit/modal.spec.js
index 52676844f4a812eb497eb4b9d78c37e526d93c76..a2077b59442df8d7d1bdcebf5f6ddac16aab4f28 100644
--- a/js/tests/unit/modal.spec.js
+++ b/js/tests/unit/modal.spec.js
@@ -450,7 +450,7 @@ describe('Modal', () => {
 
       modalEl.addEventListener('shown.bs.modal', () => {
         const keydownEscape = createEvent('keydown')
-        keydownEscape.which = 27
+        keydownEscape.key = 'Escape'
 
         modalEl.dispatchEvent(keydownEscape)
       })
@@ -479,7 +479,7 @@ describe('Modal', () => {
 
       modalEl.addEventListener('shown.bs.modal', () => {
         const keydownTab = createEvent('keydown')
-        keydownTab.which = 9
+        keydownTab.key = 'Tab'
 
         modalEl.dispatchEvent(keydownTab)
         setTimeout(expectDone, 30)
@@ -584,7 +584,7 @@ describe('Modal', () => {
 
       modalEl.addEventListener('shown.bs.modal', () => {
         const keydownEscape = createEvent('keydown')
-        keydownEscape.which = 27
+        keydownEscape.key = 'Escape'
 
         modalEl.dispatchEvent(keydownEscape)
         shownCallback()
@@ -611,7 +611,7 @@ describe('Modal', () => {
 
       modalEl.addEventListener('shown.bs.modal', () => {
         const keydownEscape = createEvent('keydown')
-        keydownEscape.which = 27
+        keydownEscape.key = 'Escape'
 
         modalEl.dispatchEvent(keydownEscape)
         shownCallback()