diff --git a/airflow-core/newsfragments/64253.significant.rst b/airflow-core/newsfragments/64253.significant.rst
new file mode 100644
index 0000000000000..9e91de9db5e82
--- /dev/null
+++ b/airflow-core/newsfragments/64253.significant.rst
@@ -0,0 +1 @@
+Rework Monaco Editor style to match Chakra UI color palette.
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index ba586990e6051..9ce143cfe7c68 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -298,6 +298,13 @@
         "title": "Bulk Delete {{resourceName}} Request Submitted"
       }
     },
+    "bulkUpdate": {
+      "error": "Bulk Update {{resourceName}} Request Failed",
+      "success": {
+        "description": "{{count}} {{resourceName}} have been successfully updated.",
+        "title": "Bulk Update {{resourceName}} Request Submitted"
+      }
+    },
     "create": {
       "error": "Create {{resourceName}} Request Failed",
       "success": {
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
index b12b5767898c9..4bc2f1c673b1e 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
@@ -28,6 +28,24 @@
       "noItemsFound": "No tasks found.",
       "title": "Affected Tasks: {{count}}"
     },
+    "bulkDelete": {
+      "button": "Delete",
+      "dialog": {
+        "confirmMessage": "This action is permanent and cannot be undone. Are you sure?",
+        "title_one": "Delete {{count}} Task Instance",
+        "title_other": "Delete {{count}} Task Instances"
+      },
+      "selected": "Selected",
+      "tooltip": "Delete selected task instances"
+    },
+    "bulkMarkAs": {
+      "button": "Mark As...",
+      "dialog": {
+        "title_one": "Mark {{count}} Task Instance As",
+        "title_other": "Mark {{count}} Task Instances As"
+      },
+      "tooltip": "Mark selected task instances as success or failed"
+    },
     "clear": {
       "button": "Clear {{type}}",
       "buttonTooltip": "Press shift+c to clear",
diff --git a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
index 836d239c1c5cc..9502522c488c4 100644
--- a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceButton.tsx
@@ -28,6 +28,7 @@ import { Tooltip } from "src/components/ui";
 import ClearTaskInstanceDialog from "./ClearTaskInstanceDialog";
 
 type Props = {
+  readonly disabled?: boolean;
   readonly groupTaskInstance?: LightGridTaskInstanceSummary;
   readonly isHotkeyEnabled?: boolean;
   // Optional: allow parent to handle opening a stable, page-level dialog
@@ -36,6 +37,7 @@ type Props = {
 };
 
 const ClearTaskInstanceButton = ({
+  disabled,
   groupTaskInstance,
   isHotkeyEnabled = false,
   onOpen,
@@ -76,6 +78,7 @@ const ClearTaskInstanceButton = ({
             type: translate("taskInstance_one"),
           })}
           colorPalette="brand"
+          disabled={disabled}
           onClick={() => (onOpen && selectedInstance ? onOpen(selectedInstance) : onOpenInternal())}
           size="md"
           variant="ghost"
diff --git a/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx b/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
index 3b9cc6f4e01a4..806b73b7d2625 100644
--- a/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
+++ b/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
@@ -19,7 +19,7 @@
 import Editor, { type EditorProps } from "@monaco-editor/react";
 import { useRef } from "react";
 
-import { useColorMode } from "src/context/colorMode";
+import { useMonacoTheme } from "src/context/colorMode";
 
 type JsonEditorProps = {
   readonly editable?: boolean;
@@ -39,7 +39,7 @@ export const JsonEditor = ({
   value,
   ...rest
 }: JsonEditorProps) => {
-  const { colorMode } = useColorMode();
+  const { beforeMount, theme } = useMonacoTheme();
   const onBlurRef = useRef(onBlur);
 
   onBlurRef.current = onBlur;
@@ -55,8 +55,6 @@ export const JsonEditor = ({
     scrollBeyondLastLine: false,
   };
 
-  const theme = colorMode === "dark" ? "vs-dark" : "vs-light";
-
   const handleChange = (val: string | undefined) => {
     onChange?.(val ?? "");
   };
@@ -72,6 +70,7 @@ export const JsonEditor = ({
       {...rest}
     >
       <Editor
+        beforeMount={beforeMount}
         height={height}
         language="json"
         onChange={handleChange}
diff --git a/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx b/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
index 8ae616232b90a..e7dbc01c3ef2f 100644
--- a/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/MarkAs/TaskInstance/MarkTaskInstanceAsButton.tsx
@@ -31,11 +31,12 @@ import { allowedStates } from "../utils";
 import MarkTaskInstanceAsDialog from "./MarkTaskInstanceAsDialog";
 
 type Props = {
+  readonly disabled?: boolean;
   readonly isHotkeyEnabled?: boolean;
   readonly taskInstance: TaskInstanceResponse;
 };
 
-const MarkTaskInstanceAsButton = ({ isHotkeyEnabled = false, taskInstance }: Props) => {
+const MarkTaskInstanceAsButton = ({ disabled, isHotkeyEnabled = false, taskInstance }: Props) => {
   const { onClose, onOpen, open } = useDisclosure();
   const { t: translate } = useTranslation();
 
@@ -74,6 +75,7 @@ const MarkTaskInstanceAsButton = ({ isHotkeyEnabled = false, taskInstance }: Pro
                   type: translate("taskInstance_one"),
                 })}
                 colorPalette="brand"
+                disabled={disabled}
                 size="md"
                 variant="ghost"
               >
diff --git a/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx b/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
index 634fecd600725..bea8e37259c9b 100644
--- a/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
+++ b/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
@@ -21,7 +21,7 @@ import Editor, { type OnMount } from "@monaco-editor/react";
 import { useCallback } from "react";
 
 import { ClipboardRoot, ClipboardIconButton } from "src/components/ui";
-import { useColorMode } from "src/context/colorMode";
+import { useMonacoTheme } from "src/context/colorMode";
 
 type Props = {
   readonly collapsed?: boolean;
@@ -31,10 +31,9 @@ type Props = {
 
 const RenderedJsonField = ({ collapsed = false, content, enableClipboard = true, ...rest }: Props) => {
   const contentFormatted = JSON.stringify(content, undefined, 2);
-  const { colorMode } = useColorMode();
+  const { beforeMount, theme } = useMonacoTheme();
   const lineCount = contentFormatted.split("\n").length;
   const height = `${Math.min(Math.max(lineCount * 19 + 10, 40), 300)}px`;
-  const theme = colorMode === "dark" ? "vs-dark" : "vs-light";
 
   const handleMount: OnMount = useCallback(
     (editorInstance) => {
@@ -46,8 +45,15 @@ const RenderedJsonField = ({ collapsed = false, content, enableClipboard = true,
   );
 
   return (
-    <Flex {...rest}>
+    <Flex
+      border="1px solid"
+      borderColor="border.emphasized"
+      borderRadius="md"
+      overflow="hidden"
+      {...rest}
+    >
       <Editor
+        beforeMount={beforeMount}
         height={height}
         language="json"
         onMount={handleMount}
diff --git a/airflow-core/src/airflow/ui/src/context/colorMode/index.ts b/airflow-core/src/airflow/ui/src/context/colorMode/index.ts
index 30edc0a3216e3..b8d579a2d7997 100644
--- a/airflow-core/src/airflow/ui/src/context/colorMode/index.ts
+++ b/airflow-core/src/airflow/ui/src/context/colorMode/index.ts
@@ -19,3 +19,4 @@
 
 export * from "./ColorModeProvider";
 export * from "./useColorMode";
+export * from "./useMonacoTheme";
diff --git a/airflow-core/src/airflow/ui/src/context/colorMode/useMonacoTheme.ts b/airflow-core/src/airflow/ui/src/context/colorMode/useMonacoTheme.ts
new file mode 100644
index 0000000000000..895d29a2fdbc6
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/context/colorMode/useMonacoTheme.ts
@@ -0,0 +1,93 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import type { Monaco } from "@monaco-editor/react";
+import { useCallback } from "react";
+
+import { useColorMode } from "./useColorMode";
+
+const cssVarToHex = (cssVar: string): string => {
+  const value = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
+  const canvas = document.createElement("canvas");
+
+  canvas.width = 1;
+  canvas.height = 1;
+  const ctx = canvas.getContext("2d");
+
+  if (!ctx || !value) {
+    return "#000000";
+  }
+  ctx.fillStyle = value;
+
+  return ctx.fillStyle;
+};
+
+const defineMonacoThemes = (monaco: Monaco) => {
+  monaco.editor.defineTheme("airflow-light", {
+    base: "vs",
+    colors: {
+      "editor.background": cssVarToHex("--chakra-colors-gray-50"),
+      "editor.foreground": cssVarToHex("--chakra-colors-gray-900"),
+      "editor.inactiveSelectionBackground": cssVarToHex("--chakra-colors-gray-200"),
+      "editor.selectionBackground": cssVarToHex("--chakra-colors-brand-200"),
+      "editorGutter.background": cssVarToHex("--chakra-colors-gray-100"),
+      "editorLineNumber.activeForeground": cssVarToHex("--chakra-colors-gray-700"),
+      "editorLineNumber.foreground": cssVarToHex("--chakra-colors-gray-400"),
+      "editorSuggestWidget.background": cssVarToHex("--chakra-colors-gray-50"),
+      "editorWidget.background": cssVarToHex("--chakra-colors-gray-50"),
+      "editorWidget.border": cssVarToHex("--chakra-colors-gray-300"),
+      "scrollbarSlider.background": cssVarToHex("--chakra-colors-gray-300"),
+      "scrollbarSlider.hoverBackground": cssVarToHex("--chakra-colors-gray-400"),
+    },
+    inherit: true,
+    rules: [],
+  });
+
+  monaco.editor.defineTheme("airflow-dark", {
+    base: "vs-dark",
+    colors: {
+      "editor.background": cssVarToHex("--chakra-colors-gray-900"),
+      "editor.foreground": cssVarToHex("--chakra-colors-gray-100"),
+      "editor.inactiveSelectionBackground": cssVarToHex("--chakra-colors-gray-800"),
+      "editor.selectionBackground": cssVarToHex("--chakra-colors-brand-800"),
+      "editorGutter.background": cssVarToHex("--chakra-colors-gray-950"),
+      "editorLineNumber.activeForeground": cssVarToHex("--chakra-colors-gray-300"),
+      "editorLineNumber.foreground": cssVarToHex("--chakra-colors-gray-500"),
+      "editorSuggestWidget.background": cssVarToHex("--chakra-colors-gray-900"),
+      "editorWidget.background": cssVarToHex("--chakra-colors-gray-900"),
+      "editorWidget.border": cssVarToHex("--chakra-colors-gray-700"),
+      "scrollbarSlider.background": cssVarToHex("--chakra-colors-gray-700"),
+      "scrollbarSlider.hoverBackground": cssVarToHex("--chakra-colors-gray-600"),
+    },
+    inherit: true,
+    rules: [],
+  });
+};
+
+export const useMonacoTheme = () => {
+  const { colorMode } = useColorMode();
+
+  const beforeMount = useCallback((monaco: Monaco) => {
+    defineMonacoThemes(monaco);
+  }, []);
+
+  return {
+    beforeMount,
+    theme: colorMode === "dark" ? "airflow-dark" : "airflow-light",
+  };
+};
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
index 3d023c9f0904f..a3dd224f890d9 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
@@ -38,7 +38,7 @@ import { ErrorAlert } from "src/components/ErrorAlert";
 import Time from "src/components/Time";
 import { ClipboardRoot, ClipboardButton, Tooltip } from "src/components/ui";
 import { ProgressBar } from "src/components/ui";
-import { useColorMode } from "src/context/colorMode";
+import { useMonacoTheme } from "src/context/colorMode";
 import useSelectedVersion from "src/hooks/useSelectedVersion";
 import { useConfig } from "src/queries/useConfig";
 import { renderDuration } from "src/utils";
@@ -115,7 +115,7 @@ export const Code = () => {
     setIsCompareDropdownOpen(false);
   };
 
-  const { colorMode } = useColorMode();
+  const { beforeMount, theme } = useMonacoTheme();
 
   useHotkeys("w", toggleWrap);
 
@@ -137,8 +137,6 @@ export const Code = () => {
     wordWrap: wrap ? "on" : "off",
   };
 
-  const theme = colorMode === "dark" ? "vs-dark" : "vs-light";
-
   const hasMultipleVersions = (dagVersions?.dag_versions.length ?? 0) >= 2;
 
   return (
@@ -278,6 +276,7 @@ export const Code = () => {
             <FileLocation fileloc={dag.fileloc} relativeFileloc={dag.relative_fileloc} />
           )}
           <Editor
+            beforeMount={beforeMount}
             language="python"
             options={editorOptions}
             theme={theme}
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
index 8ca2a6e41cbcb..08acae1b42a76 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
@@ -19,7 +19,7 @@
 import { Box } from "@chakra-ui/react";
 import { DiffEditor, type DiffEditorProps } from "@monaco-editor/react";
 
-import { useColorMode } from "src/context/colorMode";
+import { useMonacoTheme } from "src/context/colorMode";
 
 type CodeDiffViewerProps = {
   readonly height?: string;
@@ -36,7 +36,7 @@ export const CodeDiffViewer = ({
   originalCode,
   renderSideBySide = true,
 }: CodeDiffViewerProps) => {
-  const { colorMode } = useColorMode();
+  const { beforeMount, theme } = useMonacoTheme();
 
   const diffOptions: DiffEditorProps["options"] = {
     automaticLayout: true,
@@ -62,8 +62,6 @@ export const CodeDiffViewer = ({
     },
   };
 
-  const theme = colorMode === "dark" ? "vs-dark" : "vs-light";
-
   return (
     <Box
       css={{
@@ -76,6 +74,7 @@ export const CodeDiffViewer = ({
       zIndex={1}
     >
       <DiffEditor
+        beforeMount={beforeMount}
         language={language}
         modified={modifiedCode}
         options={diffOptions}
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstances/BulkDeleteTaskInstancesButton.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstances/BulkDeleteTaskInstancesButton.tsx
new file mode 100644
index 0000000000000..846dc9b7673ca
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/BulkDeleteTaskInstancesButton.tsx
@@ -0,0 +1,77 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Button, Flex, Heading, Text, useDisclosure, VStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+import { FiTrash2 } from "react-icons/fi";
+
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { Dialog } from "src/components/ui";
+import { useBulkDeleteTaskInstances } from "src/queries/useBulkDeleteTaskInstances";
+
+type Props = {
+  readonly clearSelections: VoidFunction;
+  readonly selectedRows: Map<string, boolean>;
+};
+
+const BulkDeleteTaskInstancesButton = ({ clearSelections, selectedRows }: Props) => {
+  const { t: translate } = useTranslation("dags");
+  const { onClose, onOpen, open } = useDisclosure();
+  const { bulkDelete, error, isPending } = useBulkDeleteTaskInstances({
+    clearSelections,
+    onSuccessConfirm: onClose,
+  });
+
+  return (
+    <>
+      <Button colorPalette="danger" onClick={onOpen} size="sm" variant="outline">
+        <FiTrash2 />
+        {translate("runAndTaskActions.bulkDelete.button")}
+      </Button>
+
+      <Dialog.Root onOpenChange={onClose} open={open} size="xl">
+        <Dialog.Content backdrop>
+          <Dialog.Header>
+            <VStack align="start" gap={4}>
+              <Heading size="xl">
+                {translate("runAndTaskActions.bulkDelete.dialog.title", {
+                  count: selectedRows.size,
+                })}
+              </Heading>
+            </VStack>
+          </Dialog.Header>
+          <Dialog.CloseTrigger />
+          <Dialog.Body width="full">
+            <Text color="fg" fontSize="md" fontWeight="semibold" mb={4}>
+              {translate("runAndTaskActions.bulkDelete.dialog.confirmMessage")}
+            </Text>
+            <ErrorAlert error={error} />
+            <Flex justifyContent="end" mt={3}>
+              <Button colorPalette="danger" loading={isPending} onClick={() => bulkDelete(selectedRows)}>
+                <FiTrash2 />
+                <Text fontWeight="bold">{translate("runAndTaskActions.bulkDelete.button")}</Text>
+              </Button>
+            </Flex>
+          </Dialog.Body>
+        </Dialog.Content>
+      </Dialog.Root>
+    </>
+  );
+};
+
+export default BulkDeleteTaskInstancesButton;
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstances/BulkMarkTaskInstancesButton.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstances/BulkMarkTaskInstancesButton.tsx
new file mode 100644
index 0000000000000..cf149f805301a
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/BulkMarkTaskInstancesButton.tsx
@@ -0,0 +1,90 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Button, Flex, Heading, HStack, useDisclosure, VStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+import { FiX } from "react-icons/fi";
+import { LuCheck } from "react-icons/lu";
+
+import { ErrorAlert } from "src/components/ErrorAlert";
+import { Dialog } from "src/components/ui";
+import { useBulkMarkTaskInstances } from "src/queries/useBulkMarkTaskInstances";
+
+type Props = {
+  readonly clearSelections: VoidFunction;
+  readonly selectedRows: Map<string, boolean>;
+};
+
+const BulkMarkTaskInstancesButton = ({ clearSelections, selectedRows }: Props) => {
+  const { t: translate } = useTranslation(["common", "dags"]);
+  const { onClose, onOpen, open } = useDisclosure();
+  const { bulkMark, error, isPending } = useBulkMarkTaskInstances({
+    clearSelections,
+    onSuccessConfirm: onClose,
+  });
+
+  return (
+    <>
+      <Button onClick={onOpen} size="sm" variant="outline">
+        {translate("dags:runAndTaskActions.bulkMarkAs.button")}
+      </Button>
+
+      <Dialog.Root onOpenChange={onClose} open={open} size="xl">
+        <Dialog.Content backdrop>
+          <Dialog.Header>
+            <VStack align="start" gap={4}>
+              <Heading size="xl">
+                {translate("dags:runAndTaskActions.bulkMarkAs.dialog.title", {
+                  count: selectedRows.size,
+                })}
+              </Heading>
+            </VStack>
+          </Dialog.Header>
+          <Dialog.CloseTrigger />
+          <Dialog.Body width="full">
+            <ErrorAlert error={error} />
+            <Flex justifyContent="end" mt={3}>
+              <HStack>
+                <Button
+                  colorPalette="success"
+                  loading={isPending}
+                  onClick={() => bulkMark(selectedRows, "success")}
+                  variant="outline"
+                >
+                  <LuCheck />
+                  {translate("common:states.success")}
+                </Button>
+                <Button
+                  colorPalette="danger"
+                  loading={isPending}
+                  onClick={() => bulkMark(selectedRows, "failed")}
+                  variant="outline"
+                >
+                  <FiX />
+                  {translate("common:states.failed")}
+                </Button>
+              </HStack>
+            </Flex>
+          </Dialog.Body>
+        </Dialog.Content>
+      </Dialog.Root>
+    </>
+  );
+};
+
+export default BulkMarkTaskInstancesButton;
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstances/DeleteTaskInstanceButton.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstances/DeleteTaskInstanceButton.tsx
index 39447d1d2fdb2..63c8e836deecb 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstances/DeleteTaskInstanceButton.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/DeleteTaskInstanceButton.tsx
@@ -26,10 +26,11 @@ import { Tooltip } from "src/components/ui";
 import { useDeleteTaskInstance } from "src/queries/useDeleteTaskInstance";
 
 type DeleteTaskInstanceButtonProps = {
+  readonly disabled?: boolean;
   readonly taskInstance: TaskInstanceResponse;
 };
 
-const DeleteTaskInstanceButton = ({ taskInstance }: DeleteTaskInstanceButtonProps) => {
+const DeleteTaskInstanceButton = ({ disabled, taskInstance }: DeleteTaskInstanceButtonProps) => {
   const { onClose, onOpen, open } = useDisclosure();
   const { t: translate } = useTranslation();
 
@@ -53,6 +54,7 @@ const DeleteTaskInstanceButton = ({ taskInstance }: DeleteTaskInstanceButtonProp
             type: translate("taskInstance_one"),
           })}
           colorPalette="danger"
+          disabled={disabled}
           onClick={onOpen}
           size="md"
           variant="ghost"
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
index c63cffd54c193..22ac2f56dec1e 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
@@ -29,16 +29,22 @@ import type { TaskInstanceResponse } from "openapi/requests/types.gen";
 import { ClearTaskInstanceButton } from "src/components/Clear";
 import { DagVersion } from "src/components/DagVersion";
 import { DataTable } from "src/components/DataTable";
+import { useRowSelection } from "src/components/DataTable/useRowSelection";
 import { useTableURLState } from "src/components/DataTable/useTableUrlState";
 import { ErrorAlert } from "src/components/ErrorAlert";
 import { MarkTaskInstanceAsButton } from "src/components/MarkAs";
 import { StateBadge } from "src/components/StateBadge";
 import Time from "src/components/Time";
 import { TruncatedText } from "src/components/TruncatedText";
+import { Tooltip } from "src/components/ui";
+import { ActionBar } from "src/components/ui/ActionBar";
+import { Checkbox } from "src/components/ui/Checkbox";
 import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams";
 import { useAutoRefresh, isStatePending, renderDuration } from "src/utils";
 import { getTaskInstanceLink } from "src/utils/links";
 
+import BulkDeleteTaskInstancesButton from "./BulkDeleteTaskInstancesButton";
+import BulkMarkTaskInstancesButton from "./BulkMarkTaskInstancesButton";
 import DeleteTaskInstanceButton from "./DeleteTaskInstanceButton";
 import { TaskInstancesFilter } from "./TaskInstancesFilter";
 
@@ -63,17 +69,52 @@ const {
   TRY_NUMBER: TRY_NUMBER_PARAM,
 }: SearchParamsKeysType = SearchParamsKeys;
 
+const taskKey = (ti: TaskInstanceResponse) =>
+  `${ti.dag_id}||${ti.dag_run_id}||${ti.task_id}||${ti.map_index}`;
+
 const taskInstanceColumns = ({
+  allRowsSelected,
   dagId,
+  onRowSelect,
+  onSelectAll,
   runId,
+  selectedRows,
   taskId,
   translate,
 }: {
+  allRowsSelected: boolean;
   dagId?: string;
+  onRowSelect: (key: string, isChecked: boolean) => void;
+  onSelectAll: (isChecked: boolean) => void;
   runId?: string;
+  selectedRows: Map<string, boolean>;
   taskId?: string;
   translate: TFunction;
 }): Array<ColumnDef<TaskInstanceResponse>> => [
+  {
+    accessorKey: "select",
+    cell: ({ row }) => (
+      <Checkbox
+        borderWidth={1}
+        checked={selectedRows.get(taskKey(row.original))}
+        colorPalette="brand"
+        onCheckedChange={(event) => onRowSelect(taskKey(row.original), Boolean(event.checked))}
+      />
+    ),
+    enableHiding: false,
+    enableSorting: false,
+    header: () => (
+      <Checkbox
+        borderWidth={1}
+        checked={allRowsSelected}
+        colorPalette="brand"
+        onCheckedChange={(event) => onSelectAll(Boolean(event.checked))}
+      />
+    ),
+    meta: {
+      skeletonWidth: 10,
+    },
+  },
   ...(Boolean(dagId)
     ? []
     : [
@@ -206,9 +247,9 @@ const taskInstanceColumns = ({
     accessorKey: "actions",
     cell: ({ row }) => (
       <Flex justifyContent="end">
-        <ClearTaskInstanceButton taskInstance={row.original} />
-        <MarkTaskInstanceAsButton taskInstance={row.original} />
-        <DeleteTaskInstanceButton taskInstance={row.original} />
+        <ClearTaskInstanceButton disabled={selectedRows.size > 0} taskInstance={row.original} />
+        <MarkTaskInstanceAsButton disabled={selectedRows.size > 0} taskInstance={row.original} />
+        <DeleteTaskInstanceButton disabled={selectedRows.size > 0} taskInstance={row.original} />
       </Flex>
     ),
     enableSorting: false,
@@ -292,9 +333,19 @@ export const TaskInstances = () => {
     },
   );
 
+  const { allRowsSelected, clearSelections, handleRowSelect, handleSelectAll, selectedRows } =
+    useRowSelection({
+      data: data?.task_instances,
+      getKey: taskKey,
+    });
+
   const columns = taskInstanceColumns({
+    allRowsSelected,
     dagId,
+    onRowSelect: handleRowSelect,
+    onSelectAll: handleSelectAll,
     runId,
+    selectedRows,
     taskId: Boolean(groupId) ? undefined : taskId,
     translate,
   });
@@ -312,6 +363,21 @@ export const TaskInstances = () => {
         onStateChange={setTableURLState}
         total={data?.total_entries}
       />
+      <ActionBar.Root closeOnInteractOutside={false} open={Boolean(selectedRows.size)}>
+        <ActionBar.Content>
+          <ActionBar.SelectionTrigger>
+            {selectedRows.size} {translate("dags:runAndTaskActions.bulkDelete.selected")}
+          </ActionBar.SelectionTrigger>
+          <ActionBar.Separator />
+          <Tooltip content={translate("dags:runAndTaskActions.bulkMarkAs.tooltip")}>
+            <BulkMarkTaskInstancesButton clearSelections={clearSelections} selectedRows={selectedRows} />
+          </Tooltip>
+          <Tooltip content={translate("dags:runAndTaskActions.bulkDelete.tooltip")}>
+            <BulkDeleteTaskInstancesButton clearSelections={clearSelections} selectedRows={selectedRows} />
+          </Tooltip>
+          <ActionBar.CloseTrigger onClick={clearSelections} />
+        </ActionBar.Content>
+      </ActionBar.Root>
     </>
   );
 };
diff --git a/airflow-core/src/airflow/ui/src/queries/useBulkDeleteTaskInstances.ts b/airflow-core/src/airflow/ui/src/queries/useBulkDeleteTaskInstances.ts
new file mode 100644
index 0000000000000..1f844c406854f
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/queries/useBulkDeleteTaskInstances.ts
@@ -0,0 +1,105 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+
+import {
+  useTaskInstanceServiceBulkTaskInstances,
+  useTaskInstanceServiceGetTaskInstancesKey,
+} from "openapi/queries";
+import type { BulkResponse } from "openapi/requests/types.gen";
+import { toaster } from "src/components/ui";
+
+type Props = {
+  readonly clearSelections: VoidFunction;
+  readonly onSuccessConfirm: VoidFunction;
+};
+
+const parseTaskInstanceKey = (key: string) => {
+  const [dagId = "", dagRunId = "", taskId = "", mapIndexStr] = key.split("||");
+
+  return {
+    dag_id: dagId,
+    dag_run_id: dagRunId,
+    map_index: mapIndexStr === undefined ? -1 : Number(mapIndexStr),
+    task_id: taskId,
+  };
+};
+
+export const useBulkDeleteTaskInstances = ({ clearSelections, onSuccessConfirm }: Props) => {
+  const queryClient = useQueryClient();
+  const [error, setError] = useState<unknown>(undefined);
+  const { t: translate } = useTranslation(["common", "dags"]);
+
+  const onSuccess = async (responseData: BulkResponse) => {
+    await queryClient.invalidateQueries({
+      queryKey: [useTaskInstanceServiceGetTaskInstancesKey],
+    });
+
+    if (responseData.delete) {
+      const { errors, success } = responseData.delete;
+
+      if (Array.isArray(errors) && errors.length > 0) {
+        const apiError = errors[0] as { error: string };
+
+        setError({ body: { detail: apiError.error } });
+      } else if (Array.isArray(success) && success.length > 0) {
+        toaster.create({
+          description: translate("toaster.bulkDelete.success.description", {
+            count: success.length,
+            keys: success.join(", "),
+            resourceName: translate("common:taskInstance_other"),
+          }),
+          title: translate("toaster.bulkDelete.success.title"),
+          type: "success",
+        });
+        clearSelections();
+        onSuccessConfirm();
+      }
+    }
+  };
+
+  const onError = (_error: unknown) => {
+    setError(_error);
+  };
+
+  const { isPending, mutate } = useTaskInstanceServiceBulkTaskInstances({
+    onError,
+    onSuccess,
+  });
+
+  const bulkDelete = (selectedRows: Map<string, boolean>) => {
+    mutate({
+      dagId: "~",
+      dagRunId: "~",
+      requestBody: {
+        actions: [
+          {
+            action: "delete" as const,
+            action_on_non_existence: "skip",
+            entities: [...selectedRows.keys()].map((key) => parseTaskInstanceKey(key)),
+          },
+        ],
+      },
+    });
+  };
+
+  return { bulkDelete, error, isPending };
+};
diff --git a/airflow-core/src/airflow/ui/src/queries/useBulkMarkTaskInstances.ts b/airflow-core/src/airflow/ui/src/queries/useBulkMarkTaskInstances.ts
new file mode 100644
index 0000000000000..81fa09fef903a
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/queries/useBulkMarkTaskInstances.ts
@@ -0,0 +1,107 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+
+import {
+  useTaskInstanceServiceBulkTaskInstances,
+  useTaskInstanceServiceGetTaskInstancesKey,
+} from "openapi/queries";
+import type { BulkResponse } from "openapi/requests/types.gen";
+import { toaster } from "src/components/ui";
+
+type Props = {
+  readonly clearSelections: VoidFunction;
+  readonly onSuccessConfirm: VoidFunction;
+};
+
+const parseTaskInstanceKey = (key: string) => {
+  const [dagId = "", dagRunId = "", taskId = "", mapIndexStr] = key.split("||");
+
+  return {
+    dag_id: dagId,
+    dag_run_id: dagRunId,
+    map_index: mapIndexStr === undefined ? -1 : Number(mapIndexStr),
+    task_id: taskId,
+  };
+};
+
+export const useBulkMarkTaskInstances = ({ clearSelections, onSuccessConfirm }: Props) => {
+  const queryClient = useQueryClient();
+  const [error, setError] = useState<unknown>(undefined);
+  const { t: translate } = useTranslation(["common", "dags"]);
+
+  const onSuccess = async (responseData: BulkResponse) => {
+    await queryClient.invalidateQueries({
+      queryKey: [useTaskInstanceServiceGetTaskInstancesKey],
+    });
+
+    if (responseData.update) {
+      const { errors, success } = responseData.update;
+
+      if (Array.isArray(errors) && errors.length > 0) {
+        const apiError = errors[0] as { error: string };
+
+        setError({ body: { detail: apiError.error } });
+      } else if (Array.isArray(success) && success.length > 0) {
+        toaster.create({
+          description: translate("toaster.bulkUpdate.success.description", {
+            count: success.length,
+            resourceName: translate("common:taskInstance_other"),
+          }),
+          title: translate("toaster.bulkUpdate.success.title"),
+          type: "success",
+        });
+        clearSelections();
+        onSuccessConfirm();
+      }
+    }
+  };
+
+  const onError = (_error: unknown) => {
+    setError(_error);
+  };
+
+  const { isPending, mutate } = useTaskInstanceServiceBulkTaskInstances({
+    onError,
+    onSuccess,
+  });
+
+  const bulkMark = (selectedRows: Map<string, boolean>, newState: "failed" | "success") => {
+    mutate({
+      dagId: "~",
+      dagRunId: "~",
+      requestBody: {
+        actions: [
+          {
+            action: "update" as const,
+            entities: [...selectedRows.keys()].map((key) => ({
+              ...parseTaskInstanceKey(key),
+              new_state: newState,
+            })),
+            update_mask: ["new_state"],
+          },
+        ],
+      },
+    });
+  };
+
+  return { bulkMark, error, isPending };
+};
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/TaskInstancesPage.ts b/airflow-core/src/airflow/ui/tests/e2e/pages/TaskInstancesPage.ts
index 4fde48e7f4be1..42a8f3cae3d18 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/pages/TaskInstancesPage.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/TaskInstancesPage.ts
@@ -37,7 +37,7 @@ export class TaskInstancesPage extends BasePage {
   public async navigate(): Promise<void> {
     await this.navigateTo(TaskInstancesPage.taskInstancesUrl);
     await this.page.waitForURL(/.*task_instances/, { timeout: 15_000 });
-    await this.taskInstancesTable.waitFor({ state: "visible", timeout: 10_000 });
+    await this.taskInstancesTable.waitFor({ state: "visible", timeout: 30_000 });
 
     const dataLink = this.taskInstancesTable.locator("a[href*='/dags/']").first();
     const noDataMessage = this.page.locator('text="No Task Instances found"');
