diff --git a/.circleci/config.yml b/.circleci/config.yml
index fc4816edd1a..a0e2dbd2a7a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -114,6 +114,35 @@ commands:
             source .venv/bin/activate
             python -m pytest -x test_init/test_lazy_imports.py
 
+  test_io_kaleido_v0:
+    steps:
+      - checkout
+      - browser-tools/install-chrome
+      - browser-tools/install-chromedriver
+      - run:
+          name: Install dependencies
+          command: |
+            curl -LsSf https://astral.sh/uv/install.sh | sh
+            uv venv
+            source .venv/bin/activate
+            uv pip install .
+            uv pip install -r ./test_requirements/requirements_optional.txt
+            # Install Kaleido v0 instead of the v1 specified in requirements_optional.txt
+            uv pip uninstall kaleido
+            uv pip install kaleido==0.2.1
+      - run:
+          name: List installed packages and python version
+          command: |
+            source .venv/bin/activate
+            uv pip list
+            python --version
+      - run:
+          name: Test plotly.io image output with Kaleido v0
+          command: |
+            source .venv/bin/activate
+            python -m pytest tests/test_optional/test_kaleido
+          no_output_timeout: 20m
+
 jobs:
   check-code-formatting:
     docker:
@@ -163,6 +192,17 @@ jobs:
           pandas_version: <<parameters.pandas_version>>
           numpy_version: <<parameters.numpy_version>>
 
+  test_kaleido_v0:
+    parameters:
+      python_version:
+        default: "3.12"
+        type: string
+    executor:
+      name: docker-container
+      python_version: <<parameters.python_version>>
+    steps:  
+      - test_io_kaleido_v0
+
   # Percy
   python_311_percy:
     docker:
@@ -435,5 +475,10 @@ workflows:
           python_version: "3.9"
           pandas_version: "1.2.4"
           numpy_version: "1.26.4"
+      - test_kaleido_v0:
+          matrix:
+            parameters:
+              python_version:
+                - "3.12"
       - python_311_percy
       - build-doc
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d201359d273..a338d5967d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,15 @@
 All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 
-## Unreleased
+## [6.1.0b0] - 2025-03-31
+
+### Updated
+- Add support for Kaleido >= v1.0.0 for image generation, and deprecate support for Kaleido<1 and Orca [[#5062](https://github.com/plotly/plotly.py/pull/5062)]
 
 ### Fixed
-- Fix third-party widget display issues in v6 [[#5102]https://github.com/plotly/plotly.py/pull/5102]
+- Fix third-party widget display issues in v6 [[#5102](https://github.com/plotly/plotly.py/pull/5102)]
+- Add handling for case where `jupyterlab` or `notebook` is not installed [[#5104](https://github.com/plotly/plotly.py/pull/5104/files)]
+- Fix issue causing Plotly.js script to be embedded multiple times in Jupyter notebooks [[#5112](https://github.com/plotly/plotly.py/pull/5112)]
 
 ## [6.0.1] - 2025-03-14
 
diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py
index c64cae218f0..1f4fd384c8e 100644
--- a/plotly/basedatatypes.py
+++ b/plotly/basedatatypes.py
@@ -3740,23 +3740,29 @@ def to_image(self, *args, **kwargs):
               - 'webp'
               - 'svg'
               - 'pdf'
-              - 'eps' (Requires the poppler library to be installed)
+              - 'eps' (deprecated) (Requires the poppler library to be installed)
 
-            If not specified, will default to `plotly.io.config.default_format`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_format` if engine is "kaleido"
+                - `plotly.io.orca.config.default_format` if engine is "orca" (deprecated)
 
         width: int or None
             The width of the exported image in layout pixels. If the `scale`
             property is 1.0, this will also be the width of the exported image
             in physical pixels.
 
-            If not specified, will default to `plotly.io.config.default_width`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_width` if engine is "kaleido"
+                - `plotly.io.orca.config.default_width` if engine is "orca" (deprecated)
 
         height: int or None
             The height of the exported image in layout pixels. If the `scale`
             property is 1.0, this will also be the height of the exported image
             in physical pixels.
 
-            If not specified, will default to `plotly.io.config.default_height`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_height` if engine is "kaleido"
+                - `plotly.io.orca.config.default_height` if engine is "orca" (deprecated)
 
         scale: int or float or None
             The scale factor to use when exporting the figure. A scale factor
@@ -3764,17 +3770,20 @@ def to_image(self, *args, **kwargs):
             to the figure's layout pixel dimensions. Whereas as scale factor of
             less than 1.0 will decrease the image resolution.
 
-            If not specified, will default to `plotly.io.config.default_scale`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_scale` if engine is "kaliedo"
+                - `plotly.io.orca.config.default_scale` if engine is "orca" (deprecated)
 
         validate: bool
             True if the figure should be validated before being converted to
             an image, False otherwise.
 
-        engine: str
-            Image export engine to use:
-             - "kaleido": Use Kaleido for image export
-             - "orca": Use Orca for image export
-             - "auto" (default): Use Kaleido if installed, otherwise use orca
+        engine (deprecated): str
+            Image export engine to use. This parameter is deprecated and Orca engine support will be
+            dropped in the next major Plotly version. Until then, the following values are supported:
+            - "kaleido": Use Kaleido for image export
+            - "orca": Use Orca for image export
+            - "auto" (default): Use Kaleido if installed, otherwise use Orca
 
         Returns
         -------
@@ -3782,6 +3791,26 @@ def to_image(self, *args, **kwargs):
             The image data
         """
         import plotly.io as pio
+        from plotly.io.kaleido import (
+            kaleido_available,
+            kaleido_major,
+            KALEIDO_DEPRECATION_MSG,
+            ORCA_DEPRECATION_MSG,
+            ENGINE_PARAM_DEPRECATION_MSG,
+        )
+
+        if (
+            kwargs.get("engine", None) in {None, "auto", "kaleido"}
+            and kaleido_available()
+            and kaleido_major() < 1
+        ):
+            warnings.warn(KALEIDO_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+        if kwargs.get("engine", None) == "orca":
+            warnings.warn(ORCA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+        if kwargs.get("engine", None):
+            warnings.warn(
+                ENGINE_PARAM_DEPRECATION_MSG, DeprecationWarning, stacklevel=2
+            )
 
         return pio.to_image(self, *args, **kwargs)
 
@@ -3803,25 +3832,31 @@ def write_image(self, *args, **kwargs):
               - 'webp'
               - 'svg'
               - 'pdf'
-              - 'eps' (Requires the poppler library to be installed)
+              - 'eps' (deprecated) (Requires the poppler library to be installed)
 
             If not specified and `file` is a string then this will default to the
             file extension. If not specified and `file` is not a string then this
-            will default to `plotly.io.config.default_format`
+            will default to:
+                - `plotly.io.defaults.default_format` if engine is "kaleido"
+                - `plotly.io.orca.config.default_format` if engine is "orca" (deprecated)
 
         width: int or None
             The width of the exported image in layout pixels. If the `scale`
             property is 1.0, this will also be the width of the exported image
             in physical pixels.
 
-            If not specified, will default to `plotly.io.config.default_width`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_width` if engine is "kaleido"
+                - `plotly.io.orca.config.default_width` if engine is "orca" (deprecated)
 
         height: int or None
             The height of the exported image in layout pixels. If the `scale`
             property is 1.0, this will also be the height of the exported image
             in physical pixels.
 
-            If not specified, will default to `plotly.io.config.default_height`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_height` if engine is "kaleido"
+                - `plotly.io.orca.config.default_height` if engine is "orca" (deprecated)
 
         scale: int or float or None
             The scale factor to use when exporting the figure. A scale factor
@@ -3829,23 +3864,46 @@ def write_image(self, *args, **kwargs):
             to the figure's layout pixel dimensions. Whereas as scale factor of
             less than 1.0 will decrease the image resolution.
 
-            If not specified, will default to `plotly.io.config.default_scale`
+            If not specified, will default to:
+                - `plotly.io.defaults.default_scale` if engine is "kaleido"
+                - `plotly.io.orca.config.default_scale` if engine is "orca" (deprecated)
 
         validate: bool
             True if the figure should be validated before being converted to
             an image, False otherwise.
 
-        engine: str
-            Image export engine to use:
-             - "kaleido": Use Kaleido for image export
-             - "orca": Use Orca for image export
-             - "auto" (default): Use Kaleido if installed, otherwise use orca
+        engine (deprecated): str
+            Image export engine to use. This parameter is deprecated and Orca engine support will be
+            dropped in the next major Plotly version. Until then, the following values are supported:
+            - "kaleido": Use Kaleido for image export
+            - "orca": Use Orca for image export
+            - "auto" (default): Use Kaleido if installed, otherwise use Orca
+
         Returns
         -------
         None
         """
         import plotly.io as pio
+        from plotly.io.kaleido import (
+            kaleido_available,
+            kaleido_major,
+            KALEIDO_DEPRECATION_MSG,
+            ORCA_DEPRECATION_MSG,
+            ENGINE_PARAM_DEPRECATION_MSG,
+        )
 
+        if (
+            kwargs.get("engine", None) in {None, "auto", "kaleido"}
+            and kaleido_available()
+            and kaleido_major() < 1
+        ):
+            warnings.warn(KALEIDO_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+        if kwargs.get("engine", None) == "orca":
+            warnings.warn(ORCA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+        if kwargs.get("engine", None):
+            warnings.warn(
+                ENGINE_PARAM_DEPRECATION_MSG, DeprecationWarning, stacklevel=2
+            )
         return pio.write_image(self, *args, **kwargs)
 
     # Static helpers
diff --git a/plotly/io/__init__.py b/plotly/io/__init__.py
index ef5b5ea05c7..e5b341c423c 100644
--- a/plotly/io/__init__.py
+++ b/plotly/io/__init__.py
@@ -3,7 +3,12 @@
 from typing import TYPE_CHECKING
 
 if sys.version_info < (3, 7) or TYPE_CHECKING:
-    from ._kaleido import to_image, write_image, full_figure_for_development
+    from ._kaleido import (
+        to_image,
+        write_image,
+        write_images,
+        full_figure_for_development,
+    )
     from . import orca, kaleido
     from . import json
     from ._json import to_json, from_json, read_json, write_json
@@ -11,10 +16,12 @@
     from ._html import to_html, write_html
     from ._renderers import renderers, show
     from . import base_renderers
+    from ._kaleido import defaults
 
     __all__ = [
         "to_image",
         "write_image",
+        "write_images",
         "orca",
         "json",
         "to_json",
@@ -29,6 +36,7 @@
         "show",
         "base_renderers",
         "full_figure_for_development",
+        "defaults",
     ]
 else:
     __all__, __getattr__, __dir__ = relative_import(
@@ -37,6 +45,7 @@
         [
             "._kaleido.to_image",
             "._kaleido.write_image",
+            "._kaleido.write_images",
             "._kaleido.full_figure_for_development",
             "._json.to_json",
             "._json.from_json",
@@ -48,6 +57,7 @@
             "._html.write_html",
             "._renderers.renderers",
             "._renderers.show",
+            "._kaleido.defaults",
         ],
     )
 
diff --git a/plotly/io/_defaults.py b/plotly/io/_defaults.py
new file mode 100644
index 00000000000..a2a98884c9c
--- /dev/null
+++ b/plotly/io/_defaults.py
@@ -0,0 +1,18 @@
+# Default settings for image generation
+
+
+class _Defaults(object):
+    """
+    Class to store default settings for image generation.
+    """
+
+    def __init__(self):
+        self.default_format = "png"
+        self.default_width = 700
+        self.default_height = 500
+        self.default_scale = 1
+        self.mathjax = None
+        self.topojson = None
+
+
+defaults = _Defaults()
diff --git a/plotly/io/_kaleido.py b/plotly/io/_kaleido.py
index 029b79f1029..7a5eb5df077 100644
--- a/plotly/io/_kaleido.py
+++ b/plotly/io/_kaleido.py
@@ -1,30 +1,233 @@
 import os
 import json
 from pathlib import Path
+from typing import Union, List
+import importlib.metadata as importlib_metadata
+from packaging.version import Version
+import warnings
+
 import plotly
-from plotly.io._utils import validate_coerce_fig_to_dict
+from plotly.io._utils import validate_coerce_fig_to_dict, broadcast_args_to_dicts
+from plotly.io._defaults import defaults
 
-try:
-    from kaleido.scopes.plotly import PlotlyScope
+ENGINE_SUPPORT_TIMELINE = "September 2025"
 
-    scope = PlotlyScope()
+PLOTLY_GET_CHROME_ERROR_MSG = """
 
-    # Compute absolute path to the 'plotly/package_data/' directory
-    root_dir = os.path.dirname(os.path.abspath(plotly.__file__))
-    package_dir = os.path.join(root_dir, "package_data")
-    scope.plotlyjs = os.path.join(package_dir, "plotly.min.js")
-    if scope.mathjax is None:
-        scope.mathjax = (
-            "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js"
-        )
-except ImportError:
+Kaleido requires Google Chrome to be installed. 
+
+Either download and install Chrome yourself following Google's instructions for your operating system,
+or install it from your terminal by running:
+
+    $ plotly_get_chrome
+
+"""
+
+
+# TODO: Remove --pre flag once Kaleido v1 full release is available
+KALEIDO_DEPRECATION_MSG = f"""
+Support for Kaleido versions less than 1.0.0 is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}.
+Please upgrade Kaleido to version 1.0.0 or greater (`pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`).
+"""
+ORCA_DEPRECATION_MSG = f"""
+Support for the Orca engine is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}.
+Please install Kaleido (`pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`) to use the Kaleido engine.
+"""
+ENGINE_PARAM_DEPRECATION_MSG = f"""
+Support for the 'engine' argument is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}.
+Kaleido will be the only supported engine at that time.
+"""
+
+_KALEIDO_AVAILABLE = None
+_KALEIDO_MAJOR = None
+
+kaleido_scope_default_warning_func = (
+    lambda x: f"""
+Use of plotly.io.kaleido.scope.{x} is deprecated and support will be removed after {ENGINE_SUPPORT_TIMELINE}.
+Please use plotly.io.defaults.{x} instead.
+"""
+)
+bad_attribute_error_msg_func = (
+    lambda x: f"""
+Attribute plotly.io.defaults.{x} is not valid.
+Also, use of plotly.io.kaleido.scope.* is deprecated and support will be removed after {ENGINE_SUPPORT_TIMELINE}.
+Please use plotly.io.defaults.* instead.
+"""
+)
+
+
+def kaleido_available() -> bool:
+    """
+    Returns True if any version of Kaleido is installed, otherwise False.
+    """
+    global _KALEIDO_AVAILABLE
+    global _KALEIDO_MAJOR
+    if _KALEIDO_AVAILABLE is not None:
+        return _KALEIDO_AVAILABLE
+    try:
+        import kaleido
+
+        _KALEIDO_AVAILABLE = True
+    except ImportError as e:
+        _KALEIDO_AVAILABLE = False
+    return _KALEIDO_AVAILABLE
+
+
+def kaleido_major() -> int:
+    """
+    Returns the major version number of Kaleido if it is installed,
+    otherwise raises a ValueError.
+    """
+    global _KALEIDO_MAJOR
+    if _KALEIDO_MAJOR is not None:
+        return _KALEIDO_MAJOR
+    if not kaleido_available():
+        raise ValueError("Kaleido is not installed.")
+    else:
+        _KALEIDO_MAJOR = Version(importlib_metadata.version("kaleido")).major
+    return _KALEIDO_MAJOR
+
+
+try:
+    if kaleido_available() and kaleido_major() < 1:
+        # Kaleido v0
+        import kaleido
+        from kaleido.scopes.plotly import PlotlyScope
+
+        # Show a deprecation warning if the old method of setting defaults is used
+        class PlotlyScopeWithDeprecationWarnings(PlotlyScope):
+            def __setattr__(self, name, value):
+                if name in defaults.__dict__:
+                    warnings.warn(
+                        kaleido_scope_default_warning_func(name),
+                        DeprecationWarning,
+                        stacklevel=2,
+                    )
+                super().__setattr__(name, value)
+
+            def __getattr__(self, name):
+                if hasattr(defaults, name):
+                    warnings.warn(
+                        kaleido_scope_default_warning_func(name),
+                        DeprecationWarning,
+                        stacklevel=2,
+                    )
+                return super().__getattr__(name)
+
+        # Ensure the new method of setting defaults is backwards compatible with Kaleido v0
+        # DefaultsBackwardsCompatible sets the attributes on `scope` object at the same time
+        # as they are set on the `defaults` object
+        class DefaultsBackwardsCompatible(defaults.__class__):
+            def __init__(self, scope):
+                self._scope = scope
+                super().__init__()
+
+            def __setattr__(self, name, value):
+                if not name == "_scope":
+                    if (
+                        hasattr(self._scope, name)
+                        and getattr(self._scope, name) != value
+                    ):
+                        setattr(self._scope, name, value)
+                super().__setattr__(name, value)
+
+        scope = PlotlyScopeWithDeprecationWarnings()
+        defaults = DefaultsBackwardsCompatible(scope)
+        # Compute absolute path to the 'plotly/package_data/' directory
+        root_dir = os.path.dirname(os.path.abspath(plotly.__file__))
+        package_dir = os.path.join(root_dir, "package_data")
+        scope.plotlyjs = os.path.join(package_dir, "plotly.min.js")
+        if scope.mathjax is None:
+            with warnings.catch_warnings():
+                warnings.filterwarnings(
+                    "ignore", message=r".*scope\.mathjax.*", category=DeprecationWarning
+                )
+                scope.mathjax = (
+                    "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js"
+                )
+    else:
+        # Kaleido v1
+        import kaleido
+
+        # Show a deprecation warning if the old method of setting defaults is used
+        class DefaultsDeprecationWarning:
+            def __getattr__(self, name):
+                if hasattr(defaults, name):
+                    warnings.warn(
+                        kaleido_scope_default_warning_func(name),
+                        DeprecationWarning,
+                        stacklevel=2,
+                    )
+                    return getattr(defaults, name)
+                else:
+                    raise AttributeError(bad_attribute_error_msg_func(name))
+
+            def __setattr__(self, name, value):
+                if hasattr(defaults, name):
+                    warnings.warn(
+                        kaleido_scope_default_warning_func(name),
+                        DeprecationWarning,
+                        stacklevel=2,
+                    )
+                    setattr(defaults, name, value)
+                else:
+                    raise AttributeError(bad_attribute_error_msg_func(name))
+
+        scope = DefaultsDeprecationWarning()
+
+except ImportError as e:
     PlotlyScope = None
     scope = None
 
 
+def as_path_object(file: Union[str, Path]) -> Union[Path, None]:
+    """
+    Cast the `file` argument, which may be either a string or a Path object,
+    to a Path object.
+    If `file` is neither a string nor a Path object, None will be returned.
+    """
+    if isinstance(file, str):
+        # Use the standard Path constructor to make a pathlib object.
+        path = Path(file)
+    elif isinstance(file, Path):
+        # `file` is already a Path object.
+        path = file
+    else:
+        # We could not make a Path object out of file. Either `file` is an open file
+        # descriptor with a `write()` method or it's an invalid object.
+        path = None
+    return path
+
+
+def infer_format(path: Union[Path, None], format: Union[str, None]) -> Union[str, None]:
+    if path is not None and format is None:
+        ext = path.suffix
+        if ext:
+            format = ext.lstrip(".")
+        else:
+            raise ValueError(
+                f"""
+Cannot infer image type from output path '{path}'.
+Please specify the type using the format parameter, or add a file extension.
+For example:
+
+    >>> import plotly.io as pio
+    >>> pio.write_image(fig, file_path, format='png')
+"""
+            )
+    return format
+
+
 def to_image(
-    fig, format=None, width=None, height=None, scale=None, validate=True, engine="auto"
-):
+    fig: Union[dict, plotly.graph_objects.Figure],
+    format: Union[str, None] = None,
+    width: Union[int, None] = None,
+    height: Union[int, None] = None,
+    scale: Union[int, float, None] = None,
+    validate: bool = True,
+    # Deprecated
+    engine: Union[str, None] = None,
+) -> bytes:
     """
     Convert a figure to a static image bytes string
 
@@ -35,16 +238,16 @@ def to_image(
 
     format: str or None
         The desired image format. One of
-          - 'png'
-          - 'jpg' or 'jpeg'
-          - 'webp'
-          - 'svg'
-          - 'pdf'
-          - 'eps' (Requires the poppler library to be installed and on the PATH)
+            - 'png'
+            - 'jpg' or 'jpeg'
+            - 'webp'
+            - 'svg'
+            - 'pdf'
+            - 'eps' (deprecated) (Requires the poppler library to be installed and on the PATH)
 
         If not specified, will default to:
-             - `plotly.io.kaleido.scope.default_format` if engine is "kaleido"
-             - `plotly.io.orca.config.default_format` if engine is "orca"
+            - `plotly.io.defaults.default_format` if engine is "kaleido"
+            - `plotly.io.orca.config.default_format` if engine is "orca" (deprecated)
 
     width: int or None
         The width of the exported image in layout pixels. If the `scale`
@@ -52,8 +255,8 @@ def to_image(
         in physical pixels.
 
         If not specified, will default to:
-             - `plotly.io.kaleido.scope.default_width` if engine is "kaleido"
-             - `plotly.io.orca.config.default_width` if engine is "orca"
+            - `plotly.io.defaults.default_width` if engine is "kaleido"
+            - `plotly.io.orca.config.default_width` if engine is "orca" (deprecated)
 
     height: int or None
         The height of the exported image in layout pixels. If the `scale`
@@ -61,8 +264,8 @@ def to_image(
         in physical pixels.
 
         If not specified, will default to:
-             - `plotly.io.kaleido.scope.default_height` if engine is "kaleido"
-             - `plotly.io.orca.config.default_height` if engine is "orca"
+            - `plotly.io.defaults.default_height` if engine is "kaleido"
+            - `plotly.io.orca.config.default_height` if engine is "orca" (deprecated)
 
     scale: int or float or None
         The scale factor to use when exporting the figure. A scale factor
@@ -71,29 +274,34 @@ def to_image(
         less than 1.0 will decrease the image resolution.
 
         If not specified, will default to:
-             - `plotly.io.kaleido.scope.default_scale` if engine is "kaleido"
-             - `plotly.io.orca.config.default_scale` if engine is "orca"
-
+            - `plotly.io.defaults.default_scale` if engine is "kaleido"
+            - `plotly.io.orca.config.default_scale` if engine is "orca" (deprecated)
 
     validate: bool
         True if the figure should be validated before being converted to
         an image, False otherwise.
 
-    engine: str
-        Image export engine to use:
-         - "kaleido": Use Kaleido for image export
-         - "orca": Use Orca for image export
-         - "auto" (default): Use Kaleido if installed, otherwise use orca
+    engine (deprecated): str
+        Image export engine to use. This parameter is deprecated and Orca engine support will be
+        dropped in the next major Plotly version. Until then, the following values are supported:
+          - "kaleido": Use Kaleido for image export
+          - "orca": Use Orca for image export
+          - "auto" (default): Use Kaleido if installed, otherwise use Orca
 
     Returns
     -------
     bytes
         The image data
     """
+
     # Handle engine
-    # -------------
+    if engine is not None:
+        warnings.warn(ENGINE_PARAM_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+    else:
+        engine = "auto"
+
     if engine == "auto":
-        if scope is not None:
+        if kaleido_available():
             # Default to kaleido if available
             engine = "kaleido"
         else:
@@ -109,6 +317,7 @@ def to_image(
                 engine = "kaleido"
 
     if engine == "orca":
+        warnings.warn(ORCA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
         # Fall back to legacy orca image export path
         from ._orca import to_image as to_image_orca
 
@@ -121,41 +330,77 @@ def to_image(
             validate=validate,
         )
     elif engine != "kaleido":
-        raise ValueError(
-            "Invalid image export engine specified: {engine}".format(
-                engine=repr(engine)
-            )
-        )
+        raise ValueError(f"Invalid image export engine specified: {repr(engine)}")
 
     # Raise informative error message if Kaleido is not installed
-    if scope is None:
+    if not kaleido_available():
         raise ValueError(
             """
-Image export using the "kaleido" engine requires the kaleido package,
+Image export using the "kaleido" engine requires the Kaleido package,
 which can be installed using pip:
-    $ pip install -U kaleido
+
+    $ pip install --upgrade kaleido
 """
         )
 
-    # Validate figure
-    # ---------------
+    # Convert figure to dict (and validate if requested)
     fig_dict = validate_coerce_fig_to_dict(fig, validate)
-    img_bytes = scope.transform(
-        fig_dict, format=format, width=width, height=height, scale=scale
-    )
+
+    # Request image bytes
+    if kaleido_major() > 0:
+        # Kaleido v1
+        # Check if trying to export to EPS format, which is not supported in Kaleido v1
+        if format == "eps":
+            raise ValueError(
+                f"""
+EPS export is not supported by Kaleido v1. Please use SVG or PDF instead.
+You can also downgrade to Kaleido v0, but support for Kaleido v0 will be removed after {ENGINE_SUPPORT_TIMELINE}.
+To downgrade to Kaleido v0, run:
+    $ pip install 'kaleido<1.0.0'
+"""
+            )
+        from kaleido.errors import ChromeNotFoundError
+
+        try:
+            # TODO: Refactor to make it possible to use a shared Kaleido instance here
+            img_bytes = kaleido.calc_fig_sync(
+                fig_dict,
+                opts=dict(
+                    format=format or defaults.default_format,
+                    width=width or defaults.default_width,
+                    height=height or defaults.default_height,
+                    scale=scale or defaults.default_scale,
+                ),
+                topojson=defaults.topojson,
+                kopts=dict(
+                    mathjax=defaults.mathjax,
+                )
+                if defaults.mathjax
+                else None,
+            )
+        except ChromeNotFoundError:
+            raise RuntimeError(PLOTLY_GET_CHROME_ERROR_MSG)
+
+    else:
+        # Kaleido v0
+        warnings.warn(KALEIDO_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+        img_bytes = scope.transform(
+            fig_dict, format=format, width=width, height=height, scale=scale
+        )
 
     return img_bytes
 
 
 def write_image(
-    fig,
-    file,
-    format=None,
-    scale=None,
-    width=None,
-    height=None,
-    validate=True,
-    engine="auto",
+    fig: Union[dict, plotly.graph_objects.Figure],
+    file: Union[str, Path],
+    format: Union[str, None] = None,
+    scale: Union[int, float, None] = None,
+    width: Union[int, None] = None,
+    height: Union[int, None] = None,
+    validate: bool = True,
+    # Deprecated
+    engine: Union[str, None] = "auto",
 ):
     """
     Convert a figure to a static image and write it to a file or writeable
@@ -177,13 +422,13 @@ def write_image(
           - 'webp'
           - 'svg'
           - 'pdf'
-          - 'eps' (Requires the poppler library to be installed and on the PATH)
+          - 'eps' (deprecated) (Requires the poppler library to be installed and on the PATH)
 
         If not specified and `file` is a string then this will default to the
         file extension. If not specified and `file` is not a string then this
         will default to:
-            - `plotly.io.kaleido.scope.default_format` if engine is "kaleido"
-            - `plotly.io.orca.config.default_format` if engine is "orca"
+            - `plotly.io.defaults.default_format` if engine is "kaleido"
+            - `plotly.io.orca.config.default_format` if engine is "orca" (deprecated)
 
     width: int or None
         The width of the exported image in layout pixels. If the `scale`
@@ -191,8 +436,8 @@ def write_image(
         in physical pixels.
 
         If not specified, will default to:
-            - `plotly.io.kaleido.scope.default_width` if engine is "kaleido"
-            - `plotly.io.orca.config.default_width` if engine is "orca"
+            - `plotly.io.defaults.default_width` if engine is "kaleido"
+            - `plotly.io.orca.config.default_width` if engine is "orca" (deprecated)
 
     height: int or None
         The height of the exported image in layout pixels. If the `scale`
@@ -200,8 +445,8 @@ def write_image(
         in physical pixels.
 
         If not specified, will default to:
-            - `plotly.io.kaleido.scope.default_height` if engine is "kaleido"
-            - `plotly.io.orca.config.default_height` if engine is "orca"
+            - `plotly.io.defaults.default_height` if engine is "kaleido"
+            - `plotly.io.orca.config.default_height` if engine is "orca" (deprecated)
 
     scale: int or float or None
         The scale factor to use when exporting the figure. A scale factor
@@ -210,58 +455,43 @@ def write_image(
         less than 1.0 will decrease the image resolution.
 
         If not specified, will default to:
-            - `plotly.io.kaleido.scope.default_scale` if engine is "kaleido"
-            - `plotly.io.orca.config.default_scale` if engine is "orca"
+            - `plotly.io.defaults.default_scale` if engine is "kaleido"
+            - `plotly.io.orca.config.default_scale` if engine is "orca" (deprecated)
 
     validate: bool
         True if the figure should be validated before being converted to
         an image, False otherwise.
 
-    engine: str
-        Image export engine to use:
-         - "kaleido": Use Kaleido for image export
-         - "orca": Use Orca for image export
-         - "auto" (default): Use Kaleido if installed, otherwise use orca
+    engine (deprecated): str
+        Image export engine to use. This parameter is deprecated and Orca engine support will be
+        dropped in the next major Plotly version. Until then, the following values are supported:
+          - "kaleido": Use Kaleido for image export
+          - "orca": Use Orca for image export
+          - "auto" (default): Use Kaleido if installed, otherwise use Orca
 
     Returns
     -------
     None
     """
-    # Try to cast `file` as a pathlib object `path`.
-    # ----------------------------------------------
-    if isinstance(file, str):
-        # Use the standard Path constructor to make a pathlib object.
-        path = Path(file)
-    elif isinstance(file, Path):
-        # `file` is already a Path object.
-        path = file
-    else:
-        # We could not make a Path object out of file. Either `file` is an open file
-        # descriptor with a `write()` method or it's an invalid object.
-        path = None
+    # Show Kaleido deprecation warning if needed
+    if (
+        engine in {None, "auto", "kaleido"}
+        and kaleido_available()
+        and kaleido_major() < 1
+    ):
+        warnings.warn(KALEIDO_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+    if engine == "orca":
+        warnings.warn(ORCA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
+    if engine not in {None, "auto"}:
+        warnings.warn(ENGINE_PARAM_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
 
-    # Infer format if not specified
-    # -----------------------------
-    if path is not None and format is None:
-        ext = path.suffix
-        if ext:
-            format = ext.lstrip(".")
-        else:
-            raise ValueError(
-                """
-Cannot infer image type from output path '{file}'.
-Please add a file extension or specify the type using the format parameter.
-For example:
+    # Try to cast `file` as a pathlib object `path`.
+    path = as_path_object(file)
 
-    >>> import plotly.io as pio
-    >>> pio.write_image(fig, file_path, format='png')
-""".format(
-                    file=file
-                )
-            )
+    # Infer image format if not specified
+    format = infer_format(path, format)
 
     # Request image
-    # -------------
     # Do this first so we don't create a file if image conversion fails
     img_data = to_image(
         fig,
@@ -274,7 +504,6 @@ def write_image(
     )
 
     # Open file
-    # ---------
     if path is None:
         # We previously failed to make sense of `file` as a pathlib object.
         # Attempt to write to `file` as an open file descriptor.
@@ -284,11 +513,9 @@ def write_image(
         except AttributeError:
             pass
         raise ValueError(
-            """
+            f"""
 The 'file' argument '{file}' is not a string, pathlib.Path object, or file descriptor.
-""".format(
-                file=file
-            )
+"""
         )
     else:
         # We previously succeeded in interpreting `file` as a pathlib object.
@@ -296,7 +523,154 @@ def write_image(
         path.write_bytes(img_data)
 
 
-def full_figure_for_development(fig, warn=True, as_dict=False):
+def write_images(
+    fig: Union[
+        List[Union[dict, plotly.graph_objects.Figure]],
+        Union[dict, plotly.graph_objects.Figure],
+    ],
+    file: Union[List[Union[str, Path]], Union[str, Path]],
+    format: Union[List[Union[str, None]], Union[str, None]] = None,
+    scale: Union[List[Union[int, float, None]], Union[int, float, None]] = None,
+    width: Union[List[Union[int, None]], Union[int, None]] = None,
+    height: Union[List[Union[int, None]], Union[int, None]] = None,
+    validate: Union[List[bool], bool] = True,
+) -> None:
+    """
+    Write multiple images to files or writeable objects. This is much faster than
+    calling write_image() multiple times. This function can only be used with the Kaleido
+    engine, v1.0.0 or greater.
+
+    This function accepts the same arguments as write_image() (minus the `engine` argument),
+    except that any of the arguments may be either a single value or an iterable of values.
+    If multiple arguments are iterable, they must all have the same length.
+
+    Parameters
+    ----------
+    fig:
+        Iterable of figure objects or dicts representing a figure
+
+    file: str or writeable
+        Iterables of strings or pathlib.Path objects representing local file paths to write to.
+
+    format: str or None
+        The desired image format. One of
+          - 'png'
+          - 'jpg' or 'jpeg'
+          - 'webp'
+          - 'svg'
+          - 'pdf'
+
+        If not specified, this will default to `plotly.io.defaults.default_format`.
+
+    width: int or None
+        The width of the exported image in layout pixels. If the `scale`
+        property is 1.0, this will also be the width of the exported image
+        in physical pixels.
+
+        If not specified, will default to `plotly.io.defaults.default_width`.
+
+    height: int or None
+        The height of the exported image in layout pixels. If the `scale`
+        property is 1.0, this will also be the height of the exported image
+        in physical pixels.
+
+        If not specified, will default to `plotly.io.defaults.default_height`.
+
+    scale: int or float or None
+        The scale factor to use when exporting the figure. A scale factor
+        larger than 1.0 will increase the image resolution with respect
+        to the figure's layout pixel dimensions. Whereas as scale factor of
+        less than 1.0 will decrease the image resolution.
+
+        If not specified, will default to `plotly.io.defaults.default_scale`.
+
+    validate: bool
+        True if the figure should be validated before being converted to
+        an image, False otherwise.
+
+    Returns
+    -------
+    None
+    """
+
+    # Raise informative error message if Kaleido v1 is not installed
+    if not kaleido_available():
+        raise ValueError(
+            """
+The `write_images()` function requires the Kaleido package,
+which can be installed using pip:
+
+    $ pip install --upgrade kaleido
+"""
+        )
+    elif kaleido_major() < 1:
+        raise ValueError(
+            f"""
+You have Kaleido version {Version(importlib_metadata.version("kaleido"))} installed.
+The `write_images()` function requires the Kaleido package version 1.0.0 or greater,
+which can be installed using pip:
+
+    $ pip install 'kaleido>=1.0.0'
+"""
+        )
+
+    # Broadcast arguments into correct format for passing to Kaleido
+    arg_dicts = broadcast_args_to_dicts(
+        fig=fig,
+        file=file,
+        format=format,
+        scale=scale,
+        width=width,
+        height=height,
+        validate=validate,
+    )
+
+    # For each dict:
+    #   - convert figures to dicts (and validate if requested)
+    #   - try to cast `file` as a Path object
+    for d in arg_dicts:
+        d["fig"] = validate_coerce_fig_to_dict(d["fig"], d["validate"])
+        d["file"] = as_path_object(d["file"])
+
+    # Reshape arg_dicts into correct format for passing to Kaleido
+    # We call infer_format() here rather than above so that the `file` argument
+    # has already been cast to a Path object.
+    # Also insert defaults for any missing arguments as needed
+    kaleido_specs = [
+        dict(
+            fig=d["fig"],
+            path=d["file"],
+            opts=dict(
+                format=infer_format(d["file"], d["format"]) or defaults.default_format,
+                width=d["width"] or defaults.default_width,
+                height=d["height"] or defaults.default_height,
+                scale=d["scale"] or defaults.default_scale,
+            ),
+            topojson=defaults.topojson,
+        )
+        for d in arg_dicts
+    ]
+
+    from kaleido.errors import ChromeNotFoundError
+
+    try:
+        kaleido.write_fig_from_object_sync(
+            kaleido_specs,
+            kopts=dict(
+                mathjax=defaults.mathjax,
+            )
+            if defaults.mathjax
+            else None,
+        )
+    except ChromeNotFoundError:
+        raise RuntimeError(PLOTLY_GET_CHROME_ERROR_MSG)
+
+
+def full_figure_for_development(
+    fig: Union[dict, plotly.graph_objects.Figure],
+    warn: bool = True,
+    as_dict: bool = False,
+) -> Union[plotly.graph_objects.Figure, dict]:
     """
     Compute default values for all attributes not specified in the input figure and
     returns the output as a "full" figure. This function calls Plotly.js via Kaleido
@@ -323,25 +697,39 @@ def full_figure_for_development(fig, warn=True, as_dict=False):
     """
 
     # Raise informative error message if Kaleido is not installed
-    if scope is None:
+    if not kaleido_available():
         raise ValueError(
             """
-Full figure generation requires the kaleido package,
+Full figure generation requires the Kaleido package,
 which can be installed using pip:
-    $ pip install -U kaleido
+
+    $ pip install --upgrade kaleido
 """
         )
 
     if warn:
-        import warnings
-
         warnings.warn(
             "full_figure_for_development is not recommended or necessary for "
             "production use in most circumstances. \n"
             "To suppress this warning, set warn=False"
         )
 
-    fig = json.loads(scope.transform(fig, format="json").decode("utf-8"))
+    if kaleido_available() and kaleido_major() > 0:
+        # Kaleido v1
+        bytes = kaleido.calc_fig_sync(
+            fig,
+            opts=dict(format="json"),
+        )
+        fig = json.loads(bytes.decode("utf-8"))
+    else:
+        # Kaleido v0
+        warnings.warn(
+            f"Support for Kaleido versions less than 1.0.0 is deprecated and will be removed after {ENGINE_SUPPORT_TIMELINE}. "
+            + "Please upgrade Kaleido to version 1.0.0 or greater (`pip install 'kaleido>=1.0.0'`).",
+            DeprecationWarning,
+        )
+        fig = json.loads(scope.transform(fig, format="json").decode("utf-8"))
+
     if as_dict:
         return fig
     else:
@@ -350,4 +738,90 @@ def full_figure_for_development(fig, warn=True, as_dict=False):
         return go.Figure(fig, skip_invalid=True)
 
 
+def get_chrome() -> None:
+    """
+    Install Google Chrome for Kaleido (Required for Plotly image export).
+    This function can be run from the command line using the command `plotly_get_chrome`
+    defined in pyproject.toml
+    """
+
+    usage = """
+Usage: plotly_get_chrome [-y] [--path PATH]
+
+Installs Google Chrome for Plotly image export.
+
+Options:
+  -y  Skip confirmation prompt
+  --path PATH  Specify the path to install Chrome. Must be a path to an existing directory.
+  --help  Show this message and exit.
+"""
+
+    if not kaleido_available() or kaleido_major() < 1:
+        raise ValueError("""
+This command requires Kaleido v1.0.0 or greater.
+Install it using `pip install 'kaleido>=1.0.0'` or `pip install 'plotly[kaleido]'`."
+""")
+
+    # Handle command line arguments
+    import sys
+
+    cli_args = sys.argv
+
+    # Handle "-y" flag
+    cli_yes = "-y" in cli_args
+    if cli_yes:
+        cli_args.remove("-y")
+
+    # Handle "--path" flag
+    chrome_install_path = None
+    user_specified_path = False
+    if "--path" in cli_args:
+        path_index = cli_args.index("--path") + 1
+        if path_index < len(cli_args):
+            chrome_install_path = cli_args[path_index]
+            cli_args.remove("--path")
+            cli_args.remove(chrome_install_path)
+            chrome_install_path = Path(chrome_install_path)
+            user_specified_path = True
+    else:
+        from choreographer.cli.defaults import default_download_path
+
+        chrome_install_path = default_download_path
+
+    # If install path was chosen by user, make sure there is an existing directory
+    # located at chrome_install_path; otherwise fail
+    if user_specified_path:
+        if not chrome_install_path.exists():
+            raise ValueError(f"""
+The specified install path '{chrome_install_path}' does not exist.
+Please specify a path to an existing directory using the --path argument,
+or omit the --path argument to use the default download path.
+""")
+        # Make sure the path is a directory
+        if not chrome_install_path.is_dir():
+            raise ValueError(f"""
+The specified install path '{chrome_install_path}' already exists but is not a directory.
+Please specify a path to an existing directory using the --path argument,
+or omit the --path argument to use the default download path.
+""")
+
+    # If any arguments remain, command syntax was incorrect -- print usage and exit
+    if len(cli_args) > 1:
+        print(usage)
+        sys.exit(1)
+
+    if not cli_yes:
+        print(f"""
+Plotly will install a copy of Google Chrome to be used for generating static images of plots.
+Chrome will be installed at: {chrome_install_path}
+ """)
+        response = input("Do you want to proceed? [y/n] ")
+        if not response or response[0].lower() != "y":
+            print("Cancelled")
+            return
+    print("Installing Chrome for Plotly...")
+    kaleido.get_chrome_sync(path=chrome_install_path)
+    print("Chrome installed successfully.")
+
+
 __all__ = ["to_image", "write_image", "scope", "full_figure_for_development"]
diff --git a/plotly/io/_utils.py b/plotly/io/_utils.py
index 658540ca71a..0dc84d8d07a 100644
--- a/plotly/io/_utils.py
+++ b/plotly/io/_utils.py
@@ -1,3 +1,5 @@
+from typing import List
+
 import plotly
 import plotly.graph_objs as go
 from plotly.offline import get_plotlyjs_version
@@ -43,6 +45,49 @@ def validate_coerce_output_type(output_type):
     return cls
 
 
+def broadcast_args_to_dicts(**kwargs: dict) -> List[dict]:
+    """
+    Given one or more keyword arguments which may be either a single value or a list of values,
+    return a list of keyword dictionaries by broadcasting the single valuesacross all the dicts.
+    If more than one item in the input is a list, all lists must be the same length.
+
+    Parameters
+    ----------
+    **kwargs: dict
+        The keyword arguments
+
+    Returns
+    -------
+    list of dicts
+        A list of dictionaries
+
+    Raises
+    ------
+    ValueError
+        If any of the input lists are not the same length
+    """
+    # Check that all list arguments have the same length,
+    # and find out what that length is
+    # If there are no list arguments, length is 1
+    list_lengths = [len(v) for v in tuple(kwargs.values()) if isinstance(v, list)]
+    if list_lengths and len(set(list_lengths)) > 1:
+        raise ValueError("All list arguments must have the same length.")
+    list_length = list_lengths[0] if list_lengths else 1
+
+    # Expand all arguments to lists of the same length
+    expanded_kwargs = {
+        k: [v] * list_length if not isinstance(v, list) else v
+        for k, v in kwargs.items()
+    }
+    # Reshape into a list of dictionaries
+    # Each dictionary represents the keyword arguments for a single function call
+    list_of_kwargs = [
+        {k: v[i] for k, v in expanded_kwargs.items()} for i in range(list_length)
+    ]
+
+    return list_of_kwargs
+
+
 def plotly_cdn_url(cdn_ver=get_plotlyjs_version()):
     """Return a valid plotly CDN url."""
     return "https://cdn.plot.ly/plotly-{cdn_ver}.min.js".format(
diff --git a/plotly/io/kaleido.py b/plotly/io/kaleido.py
index c14b315047b..e4df9f53c71 100644
--- a/plotly/io/kaleido.py
+++ b/plotly/io/kaleido.py
@@ -1 +1,10 @@
-from ._kaleido import to_image, write_image, scope
+from ._kaleido import (
+    to_image,
+    write_image,
+    scope,
+    kaleido_available,
+    kaleido_major,
+    KALEIDO_DEPRECATION_MSG,
+    ORCA_DEPRECATION_MSG,
+    ENGINE_PARAM_DEPRECATION_MSG,
+)
diff --git a/pyproject.toml b/pyproject.toml
index c64960354df..d02377fcb9b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,7 +38,7 @@ classifiers = [
 ]
 requires-python = ">=3.8"
 license = {file="LICENSE.txt"}
-version = "6.0.1"
+version = "6.1.0b0"
 dependencies = [
     "narwhals>=1.15.1",
     "packaging"
@@ -46,9 +46,12 @@ dependencies = [
 
 [project.optional-dependencies]
 express = ["numpy"]
-dev = [
-    "black==25.1.0"
-]
+kaleido = ["kaleido==1.0.0rc11"]
+dev = ["black==25.1.0"]
+
+[project.scripts]
+plotly_get_chrome = "plotly.io._kaleido:get_chrome"
+
 
 [tool.setuptools.packages.find]
 where = ["."]
diff --git a/test_requirements/requirements_optional.txt b/test_requirements/requirements_optional.txt
index a48fe001d56..d74e1f0def2 100644
--- a/test_requirements/requirements_optional.txt
+++ b/test_requirements/requirements_optional.txt
@@ -17,9 +17,11 @@ pyshp
 matplotlib
 scikit-image
 psutil
-kaleido
+# kaleido>=1.0.0  # Uncomment and delete line below once Kaleido v1 is released
+git+https://github.com/plotly/Kaleido.git@v1.0.0rc12#subdirectory=src/py
 orjson
 polars[timezone]
 pyarrow
 plotly-geo
 vaex;python_version<="3.9"
+pdfrw
diff --git a/tests/test_optional/test_kaleido/test_kaleido.py b/tests/test_optional/test_kaleido/test_kaleido.py
index 263fd85483a..9a9d414dc92 100644
--- a/tests/test_optional/test_kaleido/test_kaleido.py
+++ b/tests/test_optional/test_kaleido/test_kaleido.py
@@ -1,66 +1,35 @@
-import plotly.io as pio
-import plotly.io.kaleido
-from contextlib import contextmanager
-from io import BytesIO
+from io import BytesIO, StringIO
 from pathlib import Path
-from unittest.mock import Mock
-
-fig = {"layout": {"title": {"text": "figure title"}}}
-
-
-def make_writeable_mocks():
-    """Produce some mocks which we will use for testing the `write_image()` function.
-
-    These mocks should be passed as the `file=` argument to `write_image()`.
-
-    The tests should verify that the method specified in the `active_write_function`
-    attribute is called once, and that scope.transform is called with the `format=`
-    argument specified by the `.expected_format` attribute.
-
-    In total we provide two mocks: one for a writable file descriptor, and other for a
-    pathlib.Path object.
-    """
-
-    # Part 1: A mock for a file descriptor
-    # ------------------------------------
-    mock_file_descriptor = Mock()
-
-    # A file descriptor has no write_bytes method, unlike a pathlib Path.
-    del mock_file_descriptor.write_bytes
-
-    # The expected write method for a file descriptor is .write
-    mock_file_descriptor.active_write_function = mock_file_descriptor.write
+import tempfile
+from contextlib import redirect_stdout
+import base64
 
-    # Since there is no filename, there should be no format detected.
-    mock_file_descriptor.expected_format = None
-
-    # Part 2: A mock for a pathlib path
-    # ---------------------------------
-    mock_pathlib_path = Mock(spec=Path)
-
-    # A pathlib Path object has no write method, unlike a file descriptor.
-    del mock_pathlib_path.write
-
-    # The expected write method for a pathlib Path is .write_bytes
-    mock_pathlib_path.active_write_function = mock_pathlib_path.write_bytes
-
-    # Mock a path with PNG suffix
-    mock_pathlib_path.suffix = ".png"
-    mock_pathlib_path.expected_format = "png"
-
-    return mock_file_descriptor, mock_pathlib_path
-
-
-@contextmanager
-def mocked_scope():
-    # Code to acquire resource, e.g.:
-    scope_mock = Mock()
-    original_scope = pio._kaleido.scope
-    pio._kaleido.scope = scope_mock
-    try:
-        yield scope_mock
-    finally:
-        pio._kaleido.scope = original_scope
+from pdfrw import PdfReader
+from PIL import Image
+import plotly.io as pio
+from plotly.io.kaleido import kaleido_available, kaleido_major
+import pytest
+
+fig = {"data": [], "layout": {"title": {"text": "figure title"}}}
+
+
+def check_image(path_or_buffer, size=(700, 500), format="PNG"):
+    if format == "PDF":
+        img = PdfReader(path_or_buffer)
+        # TODO: There is a conversion factor needed here
+        # In Kaleido v0 the conversion factor is 0.75
+        factor = 0.75
+        expected_size = tuple(int(s * factor) for s in size)
+        actual_size = tuple(int(s) for s in img.pages[0].MediaBox[2:])
+        assert actual_size == expected_size
+    else:
+        if isinstance(path_or_buffer, (str, Path)):
+            with open(path_or_buffer, "rb") as f:
+                img = Image.open(f)
+        else:
+            img = Image.open(path_or_buffer)
+        assert img.size == size
+        assert img.format == format
 
 
 def test_kaleido_engine_to_image_returns_bytes():
@@ -75,80 +44,95 @@ def test_kaleido_fulljson():
 
 
 def test_kaleido_engine_to_image():
-    with mocked_scope() as scope:
-        pio.to_image(fig, engine="kaleido", validate=False)
+    bytes = pio.to_image(fig, engine="kaleido", validate=False)
 
-    scope.transform.assert_called_with(
-        fig, format=None, width=None, height=None, scale=None
-    )
+    # Check that image dimensions match default dimensions (700x500)
+    # and format is default format (png)
+    check_image(BytesIO(bytes))
 
 
-def test_kaleido_engine_write_image():
-    for writeable_mock in make_writeable_mocks():
-        with mocked_scope() as scope:
-            pio.write_image(fig, writeable_mock, engine="kaleido", validate=False)
+def test_kaleido_engine_write_image(tmp_path):
+    path_str = tempfile.mkstemp(suffix=".png", dir=tmp_path)[1]
+    path_path = Path(tempfile.mkstemp(suffix=".png", dir=tmp_path)[1])
 
-        scope.transform.assert_called_with(
-            fig,
-            format=writeable_mock.expected_format,
-            width=None,
-            height=None,
-            scale=None,
-        )
-
-        assert writeable_mock.active_write_function.call_count == 1
+    for out_path in [path_str, path_path]:
+        pio.write_image(fig, out_path, engine="kaleido", validate=False)
+        check_image(out_path)
 
 
 def test_kaleido_engine_to_image_kwargs():
-    with mocked_scope() as scope:
-        pio.to_image(
+    bytes = pio.to_image(
+        fig,
+        format="pdf",
+        width=700,
+        height=600,
+        scale=2,
+        engine="kaleido",
+        validate=False,
+    )
+    check_image(BytesIO(bytes), size=(700 * 2, 600 * 2), format="PDF")
+
+
+def test_kaleido_engine_write_image_kwargs(tmp_path):
+    path_str = tempfile.mkstemp(suffix=".png", dir=tmp_path)[1]
+    path_path = Path(tempfile.mkstemp(suffix=".png", dir=tmp_path)[1])
+
+    for out_path in [path_str, path_path]:
+        pio.write_image(
             fig,
-            format="pdf",
+            out_path,
+            format="jpg",
             width=700,
             height=600,
             scale=2,
             engine="kaleido",
             validate=False,
         )
-
-    scope.transform.assert_called_with(
-        fig, format="pdf", width=700, height=600, scale=2
+        check_image(out_path, size=(700 * 2, 600 * 2), format="JPEG")
+
+
+@pytest.mark.skipif(
+    not kaleido_available() or kaleido_major() < 1,
+    reason="requires Kaleido v1.0.0 or higher",
+)
+def test_kaleido_engine_write_images(tmp_path):
+    fig1 = {"data": [], "layout": {"title": {"text": "figure 1"}}}
+    fig2 = {"data": [], "layout": {"title": {"text": "figure 2"}}}
+
+    path_str = tempfile.mkstemp(suffix=".png", dir=tmp_path)[1]
+    path_path = Path(tempfile.mkstemp(suffix=".png", dir=tmp_path)[1])
+
+    pio.write_images(
+        [fig1, fig2],
+        [path_str, path_path],
+        format=["jpg", "png"],
+        width=[700, 900],
+        height=600,
+        scale=2,
+        validate=False,
     )
-
-
-def test_kaleido_engine_write_image_kwargs():
-    for writeable_mock in make_writeable_mocks():
-        with mocked_scope() as scope:
-            pio.write_image(
-                fig,
-                writeable_mock,
-                format="jpg",
-                width=700,
-                height=600,
-                scale=2,
-                engine="kaleido",
-                validate=False,
-            )
-
-        scope.transform.assert_called_with(
-            fig, format="jpg", width=700, height=600, scale=2
-        )
-
-        assert writeable_mock.active_write_function.call_count == 1
+    check_image(path_str, size=(700 * 2, 600 * 2), format="JPEG")
+    check_image(str(path_path), size=(900 * 2, 600 * 2), format="PNG")
 
 
 def test_image_renderer():
-    with mocked_scope() as scope:
-        pio.show(fig, renderer="svg", engine="kaleido", validate=False)
-
-    renderer = pio.renderers["svg"]
-    scope.transform.assert_called_with(
-        fig,
-        format="svg",
-        width=None,
-        height=None,
-        scale=renderer.scale,
+    """Verify that the image renderer returns the expected mimebundle."""
+    with redirect_stdout(StringIO()) as f:
+        pio.show(fig, renderer="png", engine="kaleido", validate=False)
+    mimebundle = f.getvalue().strip()
+    mimebundle_expected = str(
+        {
+            "image/png": base64.b64encode(
+                pio.to_image(
+                    fig,
+                    format="png",
+                    engine="kaleido",
+                    validate=False,
+                )
+            ).decode("utf8")
+        }
     )
+    assert mimebundle == mimebundle_expected
 
 
 def test_bytesio():
@@ -163,3 +147,16 @@ def test_bytesio():
     bio_bytes = bio.read()
     to_image_bytes = pio.to_image(fig, format="jpg", engine="kaleido", validate=False)
     assert bio_bytes == to_image_bytes
+
+
+def test_defaults():
+    """Test that image output defaults can be set using pio.defaults.*"""
+    try:
+        assert pio.defaults.default_format == "png"
+        pio.defaults.default_format = "svg"
+        assert pio.defaults.default_format == "svg"
+        result = pio.to_image(fig, format="svg", validate=False)
+        assert result.startswith(b"<svg")
+    finally:
+        pio.defaults.default_format = "png"
+        assert pio.defaults.default_format == "png"