diff --git a/.circleci/config.yml b/.circleci/config.yml
index c8b784ca46..09ffc838de 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,12 +8,12 @@ orbs:
 jobs:
   artifacts:
     docker:
-      - image: cimg/python:3.9.9
+      - image: cimg/python:3.10.13
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
     steps:
       - checkout
@@ -27,16 +27,16 @@ jobs:
           path: ~/dash/dash-main
           destination: /tmp/dash-main
 
-  install-dependencies-39: &install-dependencies
+  install-dependencies-310: &install-dependencies
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-node
+      - image: cimg/python:3.10.13-node
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYLINTRC: .pylintrc39
-          PYVERSION: python39
+          PYLINTRC: .pylintrc310
+          PYVERSION: python310
           PERCY_ENABLE: 0
 
     steps:
@@ -60,12 +60,11 @@ jobs:
             ls -la dash-package
           no_output_timeout: 30m
       - run:
-          name: Display npm errors and exit on failed builds
+          name: Display npm logs
           command: |
             if [ -d "/home/circleci/.npm/_logs" ]
             then
               cat /home/circleci/.npm/_logs/*
-              exit 1
             fi
       - save_cache:
           key: dep-{{ checksum ".circleci/config.yml" }}-{{ checksum "ver.txt" }}-{{ checksum "requires-all.txt" }}
@@ -88,16 +87,16 @@ jobs:
           PYVERSION: python36
           PERCY_ENABLE: 0
 
-  lint-unit-39: &lint-unit
+  lint-unit-310: &lint-unit
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYLINTRC: .pylintrc39
-          PYVERSION: python39
+          PYLINTRC: .pylintrc310
+          PYVERSION: python310
           PERCY_ENABLE: 0
 
     steps:
@@ -142,7 +141,7 @@ jobs:
           PYVERSION: python36
           PERCY_ENABLE: 0
 
-  build-windows-39:
+  build-windows-310:
     working_directory: ~/dash
     executor:
       name: win/default
@@ -157,10 +156,10 @@ jobs:
             pip install --no-cache-dir --upgrade -e .[dev,testing] --progress-bar off
             cd dash/dash-renderer && renderer build && cd ../../
 
-  test-39: &test
+  test-310: &test
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
@@ -168,7 +167,7 @@ jobs:
           PERCY_ENABLE: 1
           PERCY_PARALLEL_TOTAL: -1
           PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: True
-          PYVERSION: python39
+          PYVERSION: python310
           REDIS_URL: redis://localhost:6379
       - image: cimg/redis:6.2.6
         auth:
@@ -208,17 +207,17 @@ jobs:
       - store_artifacts:
           path: /tmp/dash_artifacts
 
-  test-39-react-18:
+  test-310-react-18:
     <<: *test
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
           PERCY_ENABLE: 0
           PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: True
-          PYVERSION: python39
+          PYVERSION: python310
           REDIS_URL: redis://localhost:6379
           REACT_VERSION: "18.2.0"
       - image: cimg/redis:6.2.6
@@ -243,15 +242,15 @@ jobs:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
 
-  dcc-lint-unit-39: &dcc-lint-unit
+  dcc-lint-unit-310: &dcc-lint-unit
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-node
+      - image: cimg/python:3.10.13-node
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
     steps:
       - checkout:
@@ -280,15 +279,15 @@ jobs:
           PYVERSION: python36
           PERCY_ENABLE: 0
 
-  dcc-39: &dcc-test
+  dcc-310: &dcc-test
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_PARALLEL_TOTAL: -1
           PERCY_ENABLE: 1
     parallelism: 3
@@ -326,15 +325,15 @@ jobs:
       - store_artifacts:
           path: /tmp/dash_artifacts
 
-  dcc-39-react-18:
+  dcc-310-react-18:
     <<: *dcc-test
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
           REACT_VERSION: "18.2.0"
 
@@ -349,15 +348,15 @@ jobs:
           PYVERSION: python36
           PERCY_ENABLE: 0
 
-  html-39: &html-test
+  html-310: &html-test
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 1
           PERCY_PARALLEL_TOTAL: -1
 
@@ -403,15 +402,15 @@ jobs:
       - store_artifacts:
           path: /tmp/dash_artifacts
 
-  html-39-react-18:
+  html-310-react-18:
     <<: *html-test
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         auth:
           username: dashautomation
           password: $DASH_PAT_DOCKERHUB
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
           REACT_VERSION: "18.2.0"
 
@@ -429,9 +428,9 @@ jobs:
   table-server: &table-server
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 1
           PERCY_PARALLEL_TOTAL: -1
 
@@ -478,18 +477,18 @@ jobs:
   table-server-react-18:
     <<: *table-server
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
           REACT_VERSION: "18.2.0"
 
   table-unit-test:
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-browsers
+      - image: cimg/python:3.10.13-browsers
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
     steps:
       - checkout:
@@ -562,9 +561,9 @@ jobs:
   table-node:
     working_directory: ~/dash
     docker:
-      - image: cimg/python:3.9.9-node
+      - image: cimg/python:3.10.13-node
         environment:
-          PYVERSION: python39
+          PYVERSION: python310
           PERCY_ENABLE: 0
     steps:
       - checkout:
@@ -602,74 +601,74 @@ workflows:
   version: 2
   tests:
     jobs:
-      - install-dependencies-39
+      - install-dependencies-310
       - install-dependencies-36
 
-      - build-windows-39
+      - build-windows-310
 
-      - lint-unit-39:
+      - lint-unit-310:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - lint-unit-36:
           requires:
             - install-dependencies-36
 
-      - test-39:
+      - test-310:
           requires:
-            - install-dependencies-39
-      - test-39-react-18:
+            - install-dependencies-310
+      - test-310-react-18:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - test-36:
           requires:
             - install-dependencies-36
 
-      - dcc-lint-unit-39:
+      - dcc-lint-unit-310:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - dcc-lint-unit-36:
           requires:
             - install-dependencies-36
 
-      - dcc-39:
+      - dcc-310:
           requires:
-            - install-dependencies-39
-      - dcc-39-react-18:
+            - install-dependencies-310
+      - dcc-310-react-18:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - dcc-36:
           requires:
             - install-dependencies-36
 
-      - html-39:
+      - html-310:
           requires:
-            - install-dependencies-39
-      - html-39-react-18:
+            - install-dependencies-310
+      - html-310-react-18:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - html-36:
           requires:
             - install-dependencies-36
 
       - table-node:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - table-unit-test:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - table-visual-test
       - table-server:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
       - table-server-react-18:
           requires:
-            - install-dependencies-39
+            - install-dependencies-310
 
       - percy/finalize_all:
           requires:
-            - test-39
-            - dcc-39
-            - html-39
+            - test-310
+            - dcc-310
+            - html-310
             - table-server
       - artifacts:
           requires:
diff --git a/.pylintrc39 b/.pylintrc310
similarity index 100%
rename from .pylintrc39
rename to .pylintrc310
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a7a68efbe..891a5982fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 All notable changes to `dash` will be documented in this file.
 This project adheres to [Semantic Versioning](https://semver.org/).
 
+## [UNRELEASED]
+
+## Changed
+
+- [#2734](https://github.com/plotly/dash/pull/2734) Configure CI for Python 3.10 [#1863](https://github.com/plotly/dash/issues/1863)
+
 ## [2.15.0] - 2024-01-31
 
 ## Added
diff --git a/components/dash-core-components/package.json b/components/dash-core-components/package.json
index 126d154305..0aaee32a3b 100644
--- a/components/dash-core-components/package.json
+++ b/components/dash-core-components/package.json
@@ -15,7 +15,7 @@
     "private::format.black": "black dash_core_components_base/ tests/ setup.py",
     "private::format.eslint": "eslint src --fix",
     "private::format.prettier": "prettier --config .prettierrc --write src/**/*.js",
-    "private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python39') !== 'python36'){process.exit(1)} \" || black --check dash_core_components_base/ tests/ setup.py",
+    "private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python310') !== 'python36'){process.exit(1)} \" || black --check dash_core_components_base/ tests/ setup.py",
     "private::lint.eslint": "eslint src",
     "private::lint.flake8": "flake8 --exclude=dash_core_components,node_modules,venv",
     "private::lint.prettier": "prettier --config .prettierrc src/**/*.js --list-different",
diff --git a/components/dash-table/package.json b/components/dash-table/package.json
index d5c291727e..8489a26599 100644
--- a/components/dash-table/package.json
+++ b/components/dash-table/package.json
@@ -23,7 +23,7 @@
     "private::format.black": "black dash_table_base tests",
     "private::lint.ts": "eslint ./src ./tests",
     "private::lint.flake": "flake8 dash_table_base tests",
-    "private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python39') !== 'python36'){process.exit(1)} \" || black --check dash_table_base tests",
+    "private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python310') !== 'python36'){process.exit(1)} \" || black --check dash_table_base tests",
     "private::lint.prettier": "prettier --config .prettierrc \"{src,tests,demo}/**/*.{js,ts,tsx}\" --list-different",
     "private::test.python": "python -m unittest tests/unit/format_test.py",
     "private::test.unit": "karma start karma.conf.js --single-run",
diff --git a/dash/testing/browser.py b/dash/testing/browser.py
index 1ce5eea68c..de4bbda788 100644
--- a/dash/testing/browser.py
+++ b/dash/testing/browser.py
@@ -159,7 +159,10 @@ def percy_snapshot(
         """
         if widths is None:
             widths = [1280]
-        snapshot_name = f"{name} - py{sys.version_info.major}.{sys.version_info.minor}"
+
+        # py3.9 hardcoded here to keep snapshot names the same accorss
+        # future python upgrades
+        snapshot_name = f"{name} - py3.9"
         logger.info("taking snapshot name => %s", snapshot_name)
         try:
             if wait_for_callbacks:
diff --git a/package.json b/package.json
index f5644f3601..d13f16150b 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
     "private::cibuild.renderer": "cd dash/dash-renderer && renderer build",
     "private::build.renderer": "cd dash/dash-renderer && renderer build",
     "private::build.jupyterlab": "cd \\@plotly/dash-jupyterlab && jlpm install && jlpm build:pack",
-    "private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python39') !== 'python36'){process.exit(1)} \" || black dash tests --exclude metadata_test.py --check",
+    "private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python310') !== 'python36'){process.exit(1)} \" || black dash tests --exclude metadata_test.py --check",
     "private::lint.flake8": "flake8 --exclude=metadata_test.py dash tests",
     "private::lint.pylint-dash": "PYLINTRC=${PYLINTRC:=.pylintrc39} && pylint dash setup.py --rcfile=$PYLINTRC",
     "private::lint.pylint-tests": "PYLINTRC=${PYLINTRC:=.pylintrc39} && pylint tests/unit tests/integration -d all --rcfile=$PYLINTRC",
diff --git a/setup.py b/setup.py
index 9a99e22641..5edd96f5e6 100644
--- a/setup.py
+++ b/setup.py
@@ -68,6 +68,7 @@ def read_req_file(req_type):
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Topic :: Database :: Front-Ends",
         "Topic :: Office/Business :: Financial :: Spreadsheet",
         "Topic :: Scientific/Engineering :: Visualization",