diff --git a/.changeset/fix-readonly-tooltips.md b/.changeset/fix-readonly-tooltips.md
new file mode 100644
index 00000000000..b84fd5e33d0
--- /dev/null
+++ b/.changeset/fix-readonly-tooltips.md
@@ -0,0 +1,5 @@
+---
+"@hashintel/petrinaut": patch
+---
+
+Fix read-only tooltips to always show during simulation mode
diff --git a/libs/@hashintel/petrinaut/src/components/button.tsx b/libs/@hashintel/petrinaut/src/components/button.tsx
new file mode 100644
index 00000000000..0fef0ad72d6
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/button.tsx
@@ -0,0 +1,82 @@
+import { cva } from "@hashintel/ds-helpers/css";
+
+import { withTooltip } from "./hoc/with-tooltip";
+
+const buttonStyle = cva({
+  base: {
+    display: "inline-flex",
+    alignItems: "center",
+    justifyContent: "center",
+    gap: "[6px]",
+    fontSize: "[12px]",
+    padding: "[4px 8px]",
+    border: "[1px solid rgba(0, 0, 0, 0.2)]",
+    borderRadius: "[4px]",
+    backgroundColor: "[white]",
+    color: "[#333]",
+    cursor: "pointer",
+    transition: "[all 0.15s ease]",
+    _hover: {
+      backgroundColor: "[rgba(0, 0, 0, 0.05)]",
+    },
+    _active: {
+      backgroundColor: "[rgba(0, 0, 0, 0.1)]",
+    },
+  },
+  variants: {
+    isDisabled: {
+      true: {
+        opacity: "[0.5]",
+        cursor: "not-allowed",
+        _hover: {
+          backgroundColor: "[white]",
+        },
+      },
+      false: {},
+    },
+    variant: {
+      default: {},
+      ghost: {
+        border: "none",
+        backgroundColor: "[transparent]",
+        _hover: {
+          backgroundColor: "[rgba(0, 0, 0, 0.05)]",
+        },
+      },
+    },
+  },
+  defaultVariants: {
+    isDisabled: false,
+    variant: "default",
+  },
+});
+
+type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
+  /** Button variant */
+  variant?: "default" | "ghost";
+  /** Button content */
+  children: React.ReactNode;
+  /** Ref to the button element */
+  ref?: React.Ref<HTMLButtonElement>;
+};
+
+const ButtonBase: React.FC<ButtonProps> = ({
+  variant = "default",
+  disabled,
+  className,
+  children,
+  ref,
+  ...props
+}) => (
+  <button
+    ref={ref}
+    type="button"
+    disabled={disabled}
+    className={`${buttonStyle({ isDisabled: disabled, variant })}${className ? ` ${className}` : ""}`}
+    {...props}
+  >
+    {children}
+  </button>
+);
+
+export const Button = withTooltip(ButtonBase, "block");
diff --git a/libs/@hashintel/petrinaut/src/components/code-editor.tsx b/libs/@hashintel/petrinaut/src/components/code-editor.tsx
new file mode 100644
index 00000000000..2c0a62d349b
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/code-editor.tsx
@@ -0,0 +1,98 @@
+import { css, cva } from "@hashintel/ds-helpers/css";
+import type { EditorProps, Monaco } from "@monaco-editor/react";
+import MonacoEditor from "@monaco-editor/react";
+import type { editor } from "monaco-editor";
+import { useCallback, useRef } from "react";
+
+import { Tooltip } from "./tooltip";
+
+const containerStyle = cva({
+  base: {
+    position: "relative",
+    border: "[1px solid rgba(0, 0, 0, 0.1)]",
+    borderRadius: "[4px]",
+    overflow: "hidden",
+  },
+  variants: {
+    isReadOnly: {
+      true: {
+        filter: "[grayscale(20%) brightness(98%)]",
+        cursor: "not-allowed",
+      },
+      false: {},
+    },
+  },
+});
+
+type CodeEditorProps = Omit<EditorProps, "theme"> & {
+  tooltip?: string;
+};
+
+/**
+ * Code editor component that wraps Monaco Editor.
+ *
+ * @param tooltip - Optional tooltip to show when hovering over the editor.
+ *                  In read-only mode, the tooltip also appears when attempting to edit.
+ */
+export const CodeEditor: React.FC<CodeEditorProps> = ({
+  tooltip,
+  options,
+  height,
+  onMount,
+  ...props
+}) => {
+  const isReadOnly = options?.readOnly === true;
+  const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
+
+  const handleMount = useCallback(
+    (editorInstance: editor.IStandaloneCodeEditor, monaco: Monaco) => {
+      editorRef.current = editorInstance;
+      // Call the original onMount if provided
+      onMount?.(editorInstance, monaco);
+    },
+    [onMount],
+  );
+
+  const editorOptions: EditorProps["options"] = {
+    minimap: { enabled: false },
+    scrollBeyondLastLine: false,
+    fontSize: 12,
+    lineNumbers: "off",
+    folding: true,
+    glyphMargin: false,
+    lineDecorationsWidth: 0,
+    lineNumbersMinChars: 3,
+    padding: { top: 8, bottom: 8 },
+    fixedOverflowWidgets: true,
+    ...options,
+  };
+
+  const editorElement = (
+    <div className={containerStyle({ isReadOnly })} style={{ height }}>
+      <MonacoEditor
+        theme="vs-light"
+        height="100%"
+        options={editorOptions}
+        onMount={handleMount}
+        {...props}
+      />
+    </div>
+  );
+
+  // Regular tooltip for non-read-only mode (if tooltip is provided)
+  if (tooltip) {
+    return (
+      <Tooltip
+        content={tooltip}
+        display="block"
+        className={css({
+          flex: "1",
+        })}
+      >
+        {editorElement}
+      </Tooltip>
+    );
+  }
+
+  return editorElement;
+};
diff --git a/libs/@hashintel/petrinaut/src/components/disabled-tooltip.tsx b/libs/@hashintel/petrinaut/src/components/disabled-tooltip.tsx
deleted file mode 100644
index 8590a0f1785..00000000000
--- a/libs/@hashintel/petrinaut/src/components/disabled-tooltip.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { ReactNode } from "react";
-
-import { UI_MESSAGES } from "../constants/ui-messages";
-import { Tooltip } from "./tooltip";
-
-interface DisabledTooltipProps {
-  /**
-   * Whether the wrapped element is disabled.
-   * When true, a tooltip explaining why will be shown on hover.
-   */
-  disabled: boolean;
-  /**
-   * The content to wrap. Should be a single element.
-   */
-  children: ReactNode;
-  /**
-   * Optional custom message. Defaults to the standard readonly mode message.
-   */
-  message?: string;
-}
-
-/**
- * Wraps children with an explanatory tooltip when disabled.
- *
- * Use this to wrap disabled form controls to explain why they're disabled
- * (e.g., during simulation mode).
- *
- * @example
- * <DisabledTooltip disabled={isReadOnly}>
- *   <input disabled={isReadOnly} ... />
- * </DisabledTooltip>
- */
-export const DisabledTooltip: React.FC<DisabledTooltipProps> = ({
-  disabled,
-  children,
-  message = UI_MESSAGES.READ_ONLY_MODE,
-}) => {
-  if (!disabled) {
-    return children;
-  }
-
-  return <Tooltip content={message}>{children}</Tooltip>;
-};
diff --git a/libs/@hashintel/petrinaut/src/components/hoc/with-tooltip.tsx b/libs/@hashintel/petrinaut/src/components/hoc/with-tooltip.tsx
new file mode 100644
index 00000000000..dac664db87d
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/hoc/with-tooltip.tsx
@@ -0,0 +1,63 @@
+import type { ComponentType } from "react";
+
+import { Tooltip } from "../tooltip";
+
+type WithTooltipProps = {
+  /** Tooltip to show when hovering (useful for explaining disabled state). */
+  tooltip?: string;
+  /**
+   * Display mode for the tooltip wrapper element.
+   * - "block": For full-width elements like inputs/selects (default)
+   * - "inline": For inline elements like buttons in flex containers
+   */
+  tooltipDisplay?: "block" | "inline";
+};
+
+/**
+ * Higher-Order Component that adds tooltip support to any component.
+ *
+ * When `tooltip` prop is provided, the component is wrapped with a Tooltip.
+ * When `tooltip` is undefined/empty, the component renders unwrapped.
+ *
+ * @param Component - The component to wrap
+ * @param defaultDisplay - Default display mode for the tooltip wrapper ("block" | "inline")
+ *
+ * @example
+ * ```tsx
+ * const MyButtonBase: React.FC<{ onClick: () => void }> = ({ onClick }) => (
+ *   <button onClick={onClick}>Click me</button>
+ * );
+ *
+ * export const MyButton = withTooltip(MyButtonBase, "inline");
+ *
+ * // Usage:
+ * <MyButton onClick={handleClick} tooltip="Click to submit" />
+ * ```
+ */
+export function withTooltip<P extends object>(
+  Component: ComponentType<P>,
+  defaultDisplay: "block" | "inline" = "block",
+): ComponentType<P & WithTooltipProps> {
+  const WrappedComponent: React.FC<P & WithTooltipProps> = ({
+    tooltip,
+    tooltipDisplay = defaultDisplay,
+    ...props
+  }) => {
+    const element = <Component {...(props as P)} />;
+
+    if (!tooltip) {
+      return element;
+    }
+
+    return (
+      <Tooltip content={tooltip} display={tooltipDisplay}>
+        {element}
+      </Tooltip>
+    );
+  };
+
+  // Set display name for debugging
+  WrappedComponent.displayName = `withTooltip(${Component.displayName ?? Component.name})`;
+
+  return WrappedComponent;
+}
diff --git a/libs/@hashintel/petrinaut/src/components/icon-button.tsx b/libs/@hashintel/petrinaut/src/components/icon-button.tsx
new file mode 100644
index 00000000000..048a519f714
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/icon-button.tsx
@@ -0,0 +1,115 @@
+import { cva } from "@hashintel/ds-helpers/css";
+
+import { withTooltip } from "./hoc/with-tooltip";
+
+const iconButtonStyle = cva({
+  base: {
+    display: "flex",
+    alignItems: "center",
+    justifyContent: "center",
+    padding: "0",
+    border: "none",
+    background: "[transparent]",
+    borderRadius: "md.4",
+    cursor: "pointer",
+    transition: "[all 0.15s ease]",
+  },
+  variants: {
+    size: {
+      sm: {
+        width: "[20px]",
+        height: "[20px]",
+        fontSize: "[14px]",
+      },
+      md: {
+        width: "[24px]",
+        height: "[24px]",
+        fontSize: "[16px]",
+      },
+      lg: {
+        width: "[32px]",
+        height: "[32px]",
+        fontSize: "[20px]",
+      },
+    },
+    variant: {
+      default: {
+        color: "gray.60",
+        _hover: {
+          color: "gray.80",
+          backgroundColor: "[rgba(0, 0, 0, 0.05)]",
+        },
+      },
+      danger: {
+        color: "gray.60",
+        _hover: {
+          color: "red.60",
+          backgroundColor: "red.10",
+        },
+      },
+    },
+    isDisabled: {
+      true: {
+        opacity: "[0.5]",
+        cursor: "not-allowed",
+        _hover: {
+          color: "gray.60",
+          backgroundColor: "[transparent]",
+        },
+      },
+      false: {},
+    },
+  },
+  defaultVariants: {
+    size: "md",
+    variant: "default",
+    isDisabled: false,
+  },
+  compoundVariants: [
+    {
+      variant: "danger",
+      isDisabled: true,
+      css: {
+        _hover: {
+          color: "gray.60",
+          backgroundColor: "[transparent]",
+        },
+      },
+    },
+  ],
+});
+
+type IconButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
+  /** Size variant */
+  size?: "sm" | "md" | "lg";
+  /** Style variant */
+  variant?: "default" | "danger";
+  /** Icon content */
+  children: React.ReactNode;
+  /** Accessibility label (required for icon-only buttons) */
+  "aria-label": string;
+  /** Ref to the button element */
+  ref?: React.Ref<HTMLButtonElement>;
+};
+
+const IconButtonBase: React.FC<IconButtonProps> = ({
+  size = "md",
+  variant = "default",
+  disabled,
+  className,
+  children,
+  ref,
+  ...props
+}) => (
+  <button
+    ref={ref}
+    type="button"
+    disabled={disabled}
+    className={`${iconButtonStyle({ size, variant, isDisabled: disabled })}${className ? ` ${className}` : ""}`}
+    {...props}
+  >
+    {children}
+  </button>
+);
+
+export const IconButton = withTooltip(IconButtonBase, "inline");
diff --git a/libs/@hashintel/petrinaut/src/components/input.tsx b/libs/@hashintel/petrinaut/src/components/input.tsx
new file mode 100644
index 00000000000..539b726f8b0
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/input.tsx
@@ -0,0 +1,71 @@
+import { cva } from "@hashintel/ds-helpers/css";
+
+import { withTooltip } from "./hoc/with-tooltip";
+
+const inputStyle = cva({
+  base: {
+    fontSize: "[14px]",
+    padding: "[6px 8px]",
+    border: "[1px solid rgba(0, 0, 0, 0.15)]",
+    borderRadius: "[4px]",
+    width: "[100%]",
+    boxSizing: "border-box",
+  },
+  variants: {
+    isDisabled: {
+      true: {
+        backgroundColor: "[rgba(0, 0, 0, 0.02)]",
+        cursor: "not-allowed",
+      },
+      false: {
+        backgroundColor: "[white]",
+        cursor: "text",
+      },
+    },
+    isMonospace: {
+      true: {
+        fontFamily: "[monospace]",
+      },
+      false: {},
+    },
+    hasError: {
+      true: {
+        borderColor: "[#ef4444]",
+      },
+      false: {},
+    },
+  },
+  defaultVariants: {
+    isDisabled: false,
+    isMonospace: false,
+    hasError: false,
+  },
+});
+
+type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> & {
+  /** Whether to use monospace font */
+  monospace?: boolean;
+  /** Whether the input has an error */
+  hasError?: boolean;
+  /** Ref to the input element */
+  ref?: React.Ref<HTMLInputElement>;
+};
+
+const InputBase: React.FC<InputProps> = ({
+  monospace = false,
+  hasError = false,
+  disabled,
+  className,
+  ref,
+  ...props
+}) => (
+  <input
+    ref={ref}
+    type="text"
+    disabled={disabled}
+    className={`${inputStyle({ isDisabled: disabled, isMonospace: monospace, hasError })}${className ? ` ${className}` : ""}`}
+    {...props}
+  />
+);
+
+export const Input = withTooltip(InputBase, "block");
diff --git a/libs/@hashintel/petrinaut/src/components/number-input.tsx b/libs/@hashintel/petrinaut/src/components/number-input.tsx
new file mode 100644
index 00000000000..c364c8d8fbb
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/number-input.tsx
@@ -0,0 +1,65 @@
+import { cva } from "@hashintel/ds-helpers/css";
+
+import { withTooltip } from "./hoc/with-tooltip";
+
+const numberInputStyle = cva({
+  base: {
+    fontSize: "[14px]",
+    padding: "[6px 8px]",
+    border: "[1px solid rgba(0, 0, 0, 0.15)]",
+    borderRadius: "[4px]",
+    width: "[100%]",
+    boxSizing: "border-box",
+    fontFamily: "[monospace]",
+  },
+  variants: {
+    isDisabled: {
+      true: {
+        backgroundColor: "[rgba(0, 0, 0, 0.02)]",
+        cursor: "not-allowed",
+      },
+      false: {
+        backgroundColor: "[white]",
+        cursor: "text",
+      },
+    },
+    hasError: {
+      true: {
+        borderColor: "[#ef4444]",
+      },
+      false: {},
+    },
+  },
+  defaultVariants: {
+    isDisabled: false,
+    hasError: false,
+  },
+});
+
+type NumberInputProps = Omit<
+  React.InputHTMLAttributes<HTMLInputElement>,
+  "type"
+> & {
+  /** Whether the input has an error */
+  hasError?: boolean;
+  /** Ref to the input element */
+  ref?: React.Ref<HTMLInputElement>;
+};
+
+const NumberInputBase: React.FC<NumberInputProps> = ({
+  hasError = false,
+  disabled,
+  className,
+  ref,
+  ...props
+}) => (
+  <input
+    ref={ref}
+    type="number"
+    disabled={disabled}
+    className={`${numberInputStyle({ isDisabled: disabled, hasError })}${className ? ` ${className}` : ""}`}
+    {...props}
+  />
+);
+
+export const NumberInput = withTooltip(NumberInputBase, "block");
diff --git a/libs/@hashintel/petrinaut/src/components/segment-group.tsx b/libs/@hashintel/petrinaut/src/components/segment-group.tsx
index 1a12574fbf4..96918c7d9eb 100644
--- a/libs/@hashintel/petrinaut/src/components/segment-group.tsx
+++ b/libs/@hashintel/petrinaut/src/components/segment-group.tsx
@@ -1,6 +1,8 @@
 import { SegmentGroup as ArkSegmentGroup } from "@ark-ui/react/segment-group";
 import { cva } from "@hashintel/ds-helpers/css";
 
+import { withTooltip } from "./hoc/with-tooltip";
+
 const containerStyle = cva({
   base: {
     display: "flex",
@@ -19,9 +21,17 @@ const containerStyle = cva({
         padding: "[3px]",
       },
     },
+    isDisabled: {
+      true: {
+        opacity: "[0.6]",
+        cursor: "not-allowed",
+      },
+      false: {},
+    },
   },
   defaultVariants: {
     size: "md",
+    isDisabled: false,
   },
 });
 
@@ -55,7 +65,6 @@ const itemStyle = cva({
     flex: "1",
     fontWeight: "medium",
     textAlign: "center",
-    cursor: "pointer",
     transition: "[all 0.2s ease]",
     position: "relative",
     zIndex: 1,
@@ -77,9 +86,19 @@ const itemStyle = cva({
         padding: "[1px 8px]",
       },
     },
+    isDisabled: {
+      true: {
+        cursor: "not-allowed",
+        pointerEvents: "none",
+      },
+      false: {
+        cursor: "pointer",
+      },
+    },
   },
   defaultVariants: {
     size: "md",
+    isDisabled: false,
   },
 });
 
@@ -94,30 +113,40 @@ interface SegmentGroupProps {
   onChange: (value: string) => void;
   /** Size variant. Defaults to "md". */
   size?: "md" | "sm";
+  /** Whether the segment group is disabled. */
+  disabled?: boolean;
 }
 
-export const SegmentGroup: React.FC<SegmentGroupProps> = ({
+const SegmentGroupBase: React.FC<SegmentGroupProps> = ({
   value,
   options,
   onChange,
   size = "md",
+  disabled = false,
 }) => {
+  const containerClassName = containerStyle({ size, isDisabled: disabled });
+
   return (
     <ArkSegmentGroup.Root
       value={value}
+      disabled={disabled}
       onValueChange={(details) => {
         if (details.value) {
           onChange(details.value);
         }
       }}
     >
-      <div className={containerStyle({ size })}>
+      <div className={containerClassName}>
         <ArkSegmentGroup.Indicator className={indicatorStyle({ size })} />
         {options.map((option) => (
           <ArkSegmentGroup.Item
             key={option.value}
             value={option.value}
-            className={itemStyle({ isSelected: value === option.value, size })}
+            className={itemStyle({
+              isSelected: value === option.value,
+              size,
+              isDisabled: disabled,
+            })}
           >
             <ArkSegmentGroup.ItemText>{option.label}</ArkSegmentGroup.ItemText>
             <ArkSegmentGroup.ItemControl />
@@ -128,3 +157,5 @@ export const SegmentGroup: React.FC<SegmentGroupProps> = ({
     </ArkSegmentGroup.Root>
   );
 };
+
+export const SegmentGroup = withTooltip(SegmentGroupBase, "block");
diff --git a/libs/@hashintel/petrinaut/src/components/select.tsx b/libs/@hashintel/petrinaut/src/components/select.tsx
new file mode 100644
index 00000000000..09594a7e8e4
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/select.tsx
@@ -0,0 +1,53 @@
+import { cva } from "@hashintel/ds-helpers/css";
+
+import { withTooltip } from "./hoc/with-tooltip";
+
+const selectStyle = cva({
+  base: {
+    fontSize: "[14px]",
+    padding: "[6px 8px]",
+    border: "[1px solid rgba(0, 0, 0, 0.1)]",
+    borderRadius: "[4px]",
+    width: "[100%]",
+    boxSizing: "border-box",
+  },
+  variants: {
+    isDisabled: {
+      true: {
+        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
+        cursor: "not-allowed",
+      },
+      false: {
+        backgroundColor: "[white]",
+        cursor: "pointer",
+      },
+    },
+  },
+  defaultVariants: {
+    isDisabled: false,
+  },
+});
+
+type SelectProps = React.SelectHTMLAttributes<HTMLSelectElement> & {
+  /** Ref to the select element */
+  ref?: React.Ref<HTMLSelectElement>;
+};
+
+const SelectBase: React.FC<SelectProps> = ({
+  disabled,
+  className,
+  children,
+  ref,
+  ...props
+}) => (
+  <select
+    ref={ref}
+    disabled={disabled}
+    className={`${selectStyle({ isDisabled: disabled })}${className ? ` ${className}` : ""}`}
+    {...props}
+  >
+    {children}
+  </select>
+);
+
+export const Select = withTooltip(SelectBase, "block");
diff --git a/libs/@hashintel/petrinaut/src/components/slider.tsx b/libs/@hashintel/petrinaut/src/components/slider.tsx
new file mode 100644
index 00000000000..384519703c1
--- /dev/null
+++ b/libs/@hashintel/petrinaut/src/components/slider.tsx
@@ -0,0 +1,48 @@
+import { css } from "@hashintel/ds-helpers/css";
+
+import { withTooltip } from "./hoc/with-tooltip";
+
+const sliderStyle = css({
+  height: "[4px]",
+  appearance: "none",
+  background: "gray.30",
+  borderRadius: "[2px]",
+  outline: "none",
+  cursor: "pointer",
+  "&:disabled": {
+    opacity: "[0.5]",
+    cursor: "not-allowed",
+  },
+  "&::-webkit-slider-thumb": {
+    appearance: "none",
+    width: "[12px]",
+    height: "[12px]",
+    borderRadius: "[50%]",
+    background: "blue.50",
+    cursor: "pointer",
+  },
+  "&::-moz-range-thumb": {
+    width: "[12px]",
+    height: "[12px]",
+    borderRadius: "[50%]",
+    background: "blue.50",
+    cursor: "pointer",
+    border: "none",
+  },
+});
+
+type SliderProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> & {
+  /** Ref to the input element */
+  ref?: React.Ref<HTMLInputElement>;
+};
+
+const SliderBase: React.FC<SliderProps> = ({ className, ref, ...props }) => (
+  <input
+    ref={ref}
+    type="range"
+    className={`${sliderStyle}${className ? ` ${className}` : ""}`}
+    {...props}
+  />
+);
+
+export const Slider = withTooltip(SliderBase, "inline");
diff --git a/libs/@hashintel/petrinaut/src/components/switch.tsx b/libs/@hashintel/petrinaut/src/components/switch.tsx
index d0bb2ac4ac8..23ec285075c 100644
--- a/libs/@hashintel/petrinaut/src/components/switch.tsx
+++ b/libs/@hashintel/petrinaut/src/components/switch.tsx
@@ -1,58 +1,65 @@
 import { Switch as ArkSwitch } from "@ark-ui/react/switch";
 import { css } from "@hashintel/ds-helpers/css";
 
+import { withTooltip } from "./hoc/with-tooltip";
+
+const controlStyle = css({
+  position: "relative",
+  display: "inline-block",
+  width: "[34px]",
+  height: "[20px]",
+  borderRadius: "[10px]",
+  transition: "[all 0.2s ease]",
+  backgroundColor: "gray.40",
+  _checked: {
+    backgroundColor: "green.40",
+  },
+  _disabled: {
+    cursor: "not-allowed",
+  },
+  _enabled: {
+    cursor: "pointer",
+  },
+});
+
+const thumbStyle = css({
+  position: "absolute",
+  top: "[3px]",
+  left: "[3px]",
+  width: "[14px]",
+  height: "[14px]",
+  borderRadius: "[7px]",
+  backgroundColor: "[white]",
+  boxShadow: "[0 2px 4px rgba(0,0,0,0.2)]",
+  transition: "[all 0.2s ease]",
+  "&[data-state='checked']": {
+    transform: "[translateX(14px)]",
+  },
+});
+
 interface SwitchProps {
   checked?: boolean;
   onCheckedChange?: (checked: boolean) => void;
   disabled?: boolean;
 }
 
-export const Switch: React.FC<SwitchProps> = ({
+const SwitchBase: React.FC<SwitchProps> = ({
   checked,
   onCheckedChange,
   disabled = false,
-}) => {
-  return (
-    <ArkSwitch.Root
-      checked={checked}
-      onCheckedChange={(details) => {
-        onCheckedChange?.(details.checked);
-      }}
-      disabled={disabled}
-    >
-      <ArkSwitch.Control
-        className={css({
-          position: "relative",
-          display: "inline-block",
-          width: "[34px]",
-          height: "[20px]",
-          borderRadius: "[10px]",
-          cursor: disabled ? "not-allowed" : "pointer",
-          transition: "[all 0.2s ease]",
-          backgroundColor: "gray.40",
-          _checked: {
-            backgroundColor: "green.40",
-          },
-        })}
-      >
-        <ArkSwitch.Thumb
-          className={css({
-            position: "absolute",
-            top: "[3px]",
-            left: "[3px]",
-            width: "[14px]",
-            height: "[14px]",
-            borderRadius: "[7px]",
-            backgroundColor: "[white]",
-            boxShadow: "[0 2px 4px rgba(0,0,0,0.2)]",
-            transition: "[all 0.2s ease]",
-            "&[data-state='checked']": {
-              transform: "[translateX(14px)]",
-            },
-          })}
-        />
-      </ArkSwitch.Control>
-      <ArkSwitch.HiddenInput />
-    </ArkSwitch.Root>
-  );
-};
+}) => (
+  <ArkSwitch.Root
+    checked={checked}
+    onCheckedChange={(details) => {
+      onCheckedChange?.(details.checked);
+    }}
+    disabled={disabled}
+  >
+    <ArkSwitch.Control className={controlStyle}>
+      <ArkSwitch.Thumb className={thumbStyle} />
+    </ArkSwitch.Control>
+    <ArkSwitch.HiddenInput />
+  </ArkSwitch.Root>
+);
+
+export const Switch = withTooltip(SwitchBase, "inline");
diff --git a/libs/@hashintel/petrinaut/src/components/tooltip.tsx b/libs/@hashintel/petrinaut/src/components/tooltip.tsx
index f22f9c38cd2..880bb925ce6 100644
--- a/libs/@hashintel/petrinaut/src/components/tooltip.tsx
+++ b/libs/@hashintel/petrinaut/src/components/tooltip.tsx
@@ -1,5 +1,6 @@
+import { ark } from "@ark-ui/react/factory";
 import { Tooltip as ArkTooltip } from "@ark-ui/react/tooltip";
-import { css } from "@hashintel/ds-helpers/css";
+import { css, cva, cx } from "@hashintel/ds-helpers/css";
 import type { SvgIconProps } from "@mui/material";
 import { SvgIcon, Tooltip as MuiTooltip } from "@mui/material";
 import type { FunctionComponent, ReactNode } from "react";
@@ -14,19 +15,69 @@ const tooltipContentStyle = css({
   padding: "[6px 10px]",
 });
 
+const triggerWrapperStyle = cva({
+  variants: {
+    display: {
+      /** For block-level elements like inputs, selects - takes full width */
+      block: {
+        display: "block",
+      },
+      /** For inline elements like buttons in flex containers */
+      inline: {
+        display: "inline-block",
+      },
+    },
+  },
+  defaultVariants: {
+    display: "block",
+  },
+});
+
 interface TooltipProps {
-  content: string;
+  /**
+   * The tooltip content. When empty/undefined, children are rendered without tooltip wrapper.
+   */
+  content?: string;
   children: ReactNode;
+  /**
+   * Display mode for the wrapper element.
+   * - "block": For full-width elements like inputs/selects (default)
+   * - "inline": For inline elements like buttons in flex containers
+   */
+  display?: "block" | "inline";
+  /**
+   * Optional className to apply to the trigger wrapper element.
+   */
+  className?: string;
 }
 
-export const Tooltip: React.FC<TooltipProps> = ({ content, children }) => {
+/**
+ * Tooltip component that wraps children and shows a tooltip on hover.
+ *
+ * Uses a wrapper element to capture pointer events, enabling tooltips on disabled elements.
+ * Set `display="inline"` when wrapping inline elements like buttons.
+ */
+export const Tooltip: React.FC<TooltipProps> = ({
+  content,
+  children,
+  display = "block",
+  className,
+}) => {
+  if (!content) {
+    return children;
+  }
+
   return (
     <ArkTooltip.Root
       openDelay={200}
       closeDelay={0}
       positioning={{ placement: "top" }}
     >
-      <ArkTooltip.Trigger asChild>{children}</ArkTooltip.Trigger>
+      <ArkTooltip.Trigger asChild>
+        <ark.span className={cx(triggerWrapperStyle({ display }), className)}>
+          {children}
+        </ark.span>
+      </ArkTooltip.Trigger>
       <ArkTooltip.Positioner>
         <ArkTooltip.Content className={tooltipContentStyle}>
           {content}
diff --git a/libs/@hashintel/petrinaut/src/constants/ui-messages.ts b/libs/@hashintel/petrinaut/src/constants/ui-messages.ts
index 4123bcf09ed..fb01c021686 100644
--- a/libs/@hashintel/petrinaut/src/constants/ui-messages.ts
+++ b/libs/@hashintel/petrinaut/src/constants/ui-messages.ts
@@ -3,5 +3,6 @@
  */
 export const UI_MESSAGES = {
   AI_FEATURE_COMING_SOON: "AI generation feature coming soon",
+  DYNAMICS_REQUIRES_TYPE: "Select a token type to enable dynamics",
   READ_ONLY_MODE: "Editing disabled while in simulation mode",
 } as const;
diff --git a/libs/@hashintel/petrinaut/src/state/use-is-read-only.ts b/libs/@hashintel/petrinaut/src/state/use-is-read-only.ts
index 09c87863f16..d911f11e673 100644
--- a/libs/@hashintel/petrinaut/src/state/use-is-read-only.ts
+++ b/libs/@hashintel/petrinaut/src/state/use-is-read-only.ts
@@ -8,17 +8,19 @@ import { EditorContext } from "./editor-context";
  *
  * The editor is read-only when:
  * 1. The global mode is "simulate" (user has switched to simulation mode), OR
- * 2. A simulation is currently running or paused (has been initialized)
+ * 2. A simulation is currently running, paused, or complete
  *
  * When read-only, structural changes to the SDCPN (places, transitions, arcs, etc.)
- * are prevented to maintain consistency with the running simulation.
+ * are prevented to maintain consistency with the simulation.
  */
 export const useIsReadOnly = (): boolean => {
   const { globalMode } = use(EditorContext);
   const { state: simulationState } = use(SimulationContext);
 
   const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
+    simulationState === "Running" ||
+    simulationState === "Paused" ||
+    simulationState === "Complete";
 
   const isReadOnly = globalMode === "simulate" || isSimulationActive;
   return isReadOnly;
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/toolbar-button.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/toolbar-button.tsx
index 87e7a7a0917..c9c78f0b1c0 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/toolbar-button.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/components/BottomBar/toolbar-button.tsx
@@ -87,7 +87,7 @@ export const ToolbarButton: React.FC<ToolbarButtonProps> = ({
   };
 
   return (
-    <Tooltip content={tooltip}>
+    <Tooltip content={tooltip} display="inline">
       <button
         type="button"
         onClick={onClick}
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/components/mode-selector.tsx b/libs/@hashintel/petrinaut/src/views/Editor/components/mode-selector.tsx
index 3bfe0e578a1..f04d620a8e6 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/components/mode-selector.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/components/mode-selector.tsx
@@ -137,6 +137,7 @@ export const ModeSelector: React.FC<ModeSelectorProps> = ({
                 <Tooltip
                   key={option.value}
                   content="Simulate tab is disabled temporarily. Simulation is available in Edit Mode."
+                  display="inline"
                 >
                   <span>{item}</span>
                 </Tooltip>
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/differential-equation-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/differential-equation-properties.tsx
index fe9e0238f2d..018a4ca9b3d 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/differential-equation-properties.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/differential-equation-properties.tsx
@@ -1,11 +1,12 @@
 /* eslint-disable id-length */
 
 import { css, cva } from "@hashintel/ds-helpers/css";
-import MonacoEditor from "@monaco-editor/react";
 import { useState } from "react";
 import { TbDotsVertical, TbSparkles } from "react-icons/tb";
 
-import { DisabledTooltip } from "../../../../components/disabled-tooltip";
+import { Button } from "../../../../components/button";
+import { CodeEditor } from "../../../../components/code-editor";
+import { Input } from "../../../../components/input";
 import { Menu } from "../../../../components/menu";
 import { Tooltip } from "../../../../components/tooltip";
 import { UI_MESSAGES } from "../../../../constants/ui-messages";
@@ -39,51 +40,20 @@ const fieldLabelStyle = css({
   marginBottom: "[4px]",
 });
 
-const inputStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-    boxSizing: "border-box",
-  },
-  variants: {
-    isReadOnly: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-  },
-});
-
 const typeDropdownButtonStyle = cva({
   base: {
     width: "[100%]",
     fontSize: "[14px]",
     padding: "[6px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
     display: "flex",
-    alignItems: "center",
+    justifyContent: "flex-start",
     gap: "[8px]",
     textAlign: "left",
   },
   variants: {
-    isReadOnly: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "pointer",
-      },
+    isDisabled: {
+      true: {},
+      false: {},
     },
   },
 });
@@ -95,6 +65,10 @@ const colorDotStyle = css({
   flexShrink: 0,
 });
 
+const placeholderStyle = css({
+  color: "[rgba(0, 0, 0, 0.4)]",
+});
+
 const dropdownMenuStyle = css({
   position: "absolute",
   top: "[100%]",
@@ -223,28 +197,6 @@ const menuButtonStyle = css({
   color: "[rgba(0, 0, 0, 0.6)]",
 });
 
-const editorContainerStyle = cva({
-  base: {
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    overflow: "hidden",
-    flex: "[1]",
-    minHeight: "[0]",
-  },
-  variants: {
-    isReadOnly: {
-      true: {
-        filter: "[grayscale(20%) brightness(98%)]",
-        pointerEvents: "none",
-      },
-      false: {
-        filter: "[none]",
-        pointerEvents: "auto",
-      },
-    },
-  },
-});
-
 const aiMenuItemStyle = css({
   display: "flex",
   alignItems: "center",
@@ -332,83 +284,89 @@ export const DifferentialEquationProperties: React.FC<
 
       <div>
         <div className={fieldLabelStyle}>Name</div>
-        <DisabledTooltip disabled={isReadOnly}>
-          <input
-            type="text"
-            value={differentialEquation.name}
-            onChange={(event) => {
-              updateDifferentialEquation(
-                differentialEquation.id,
-                (existingEquation) => {
-                  existingEquation.name = event.target.value;
-                },
-              );
-            }}
-            disabled={isReadOnly}
-            className={inputStyle({ isReadOnly })}
-          />
-        </DisabledTooltip>
+        <Input
+          value={differentialEquation.name}
+          onChange={(event) => {
+            updateDifferentialEquation(
+              differentialEquation.id,
+              (existingEquation) => {
+                existingEquation.name = event.target.value;
+              },
+            );
+          }}
+          disabled={isReadOnly}
+          tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
 
       <div>
         <div className={fieldLabelStyle}>Associated Type</div>
         <div style={{ position: "relative" }}>
-          <DisabledTooltip disabled={isReadOnly}>
-            <button
-              type="button"
-              onClick={() => setShowTypeDropdown(!showTypeDropdown)}
-              onBlur={() => setTimeout(() => setShowTypeDropdown(false), 200)}
-              disabled={isReadOnly}
-              className={typeDropdownButtonStyle({ isReadOnly })}
-            >
-              {associatedType && (
-                <>
-                  <div
-                    className={colorDotStyle}
-                    style={{ backgroundColor: associatedType.displayColor }}
-                  />
-                  <span>{associatedType.name}</span>
-                </>
-              )}
-            </button>
-          </DisabledTooltip>
+          <Button
+            onClick={() => setShowTypeDropdown(!showTypeDropdown)}
+            onBlur={() => setTimeout(() => setShowTypeDropdown(false), 200)}
+            disabled={isReadOnly}
+            className={typeDropdownButtonStyle({ isDisabled: isReadOnly })}
+            tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+          >
+            {associatedType ? (
+              <>
+                <div
+                  className={colorDotStyle}
+                  style={{ backgroundColor: associatedType.displayColor }}
+                />
+                <span>{associatedType.name}</span>
+              </>
+            ) : (
+              <span className={placeholderStyle}>Select a type</span>
+            )}
+          </Button>
           {showTypeDropdown && !isReadOnly && (
             <div className={dropdownMenuStyle}>
-              {types.map((type) => (
-                <button
-                  key={type.id}
-                  type="button"
-                  onClick={() => {
-                    handleTypeChange(type.id);
-                    setShowTypeDropdown(false);
-                  }}
+              {types.length === 0 ? (
+                <div
                   className={dropdownItemStyle}
-                  style={{
-                    backgroundColor:
-                      type.id === differentialEquation.colorId
-                        ? "rgba(0, 0, 0, 0.05)"
-                        : "transparent",
-                  }}
-                  onMouseEnter={(event) => {
-                    // eslint-disable-next-line no-param-reassign
-                    event.currentTarget.style.backgroundColor =
-                      "rgba(0, 0, 0, 0.05)";
-                  }}
-                  onMouseLeave={(event) => {
-                    // eslint-disable-next-line no-param-reassign
-                    event.currentTarget.style.backgroundColor =
-                      type.id === differentialEquation.colorId
-                        ? "rgba(0, 0, 0, 0.05)"
-                        : "transparent";
-                  }}
+                  style={{ color: "rgba(0, 0, 0, 0.4)" }}
                 >
-                  <div
-                    className={colorDotStyle}
-                    style={{ backgroundColor: type.displayColor }}
-                  />
-                  <span>{type.name}</span>
-                </button>
-              ))}
+                  Create a type first
+                </div>
+              ) : (
+                types.map((type) => (
+                  <button
+                    key={type.id}
+                    type="button"
+                    onClick={() => {
+                      handleTypeChange(type.id);
+                      setShowTypeDropdown(false);
+                    }}
+                    className={dropdownItemStyle}
+                    style={{
+                      backgroundColor:
+                        type.id === differentialEquation.colorId
+                          ? "rgba(0, 0, 0, 0.05)"
+                          : "transparent",
+                    }}
+                    onMouseEnter={(event) => {
+                      // eslint-disable-next-line no-param-reassign
+                      event.currentTarget.style.backgroundColor =
+                        "rgba(0, 0, 0, 0.05)";
+                    }}
+                    onMouseLeave={(event) => {
+                      // eslint-disable-next-line no-param-reassign
+                      event.currentTarget.style.backgroundColor =
+                        type.id === differentialEquation.colorId
+                          ? "rgba(0, 0, 0, 0.05)"
+                          : "transparent";
+                    }}
+                  >
+                    <div
+                      className={colorDotStyle}
+                      style={{ backgroundColor: type.displayColor }}
+                    />
+                    <span>{type.name}</span>
+                  </button>
+                ))
+              )}
             </div>
           )}
         </div>
@@ -449,20 +407,15 @@ export const DifferentialEquationProperties: React.FC<
               you want to continue?
             </div>
             <div className={confirmDialogButtonsStyle}>
-              <button
-                type="button"
-                onClick={cancelTypeChange}
-                className={cancelButtonStyle}
-              >
+              <Button onClick={cancelTypeChange} className={cancelButtonStyle}>
                 Cancel
-              </button>
-              <button
-                type="button"
+              </Button>
+              <Button
                 onClick={confirmTypeChange}
                 className={confirmButtonStyle}
               >
                 Change Type
-              </button>
+              </Button>
             </div>
           </div>
         </div>
@@ -503,7 +456,10 @@ export const DifferentialEquationProperties: React.FC<
                 {
                   id: "generate-ai",
                   label: (
-                    <Tooltip content={UI_MESSAGES.AI_FEATURE_COMING_SOON}>
+                    <Tooltip
+                      content={UI_MESSAGES.AI_FEATURE_COMING_SOON}
+                      display="inline"
+                    >
                       <div className={aiMenuItemStyle}>
                         <TbSparkles className={aiIconStyle} />
                         Generate with AI
@@ -519,37 +475,22 @@ export const DifferentialEquationProperties: React.FC<
             />
           )}
         </div>
-        <DisabledTooltip disabled={isReadOnly}>
-          <div className={editorContainerStyle({ isReadOnly })}>
-            <MonacoEditor
-              language="typescript"
-              value={differentialEquation.code}
-              onChange={(newCode) => {
-                updateDifferentialEquation(
-                  differentialEquation.id,
-                  (existingEquation) => {
-                    existingEquation.code = newCode ?? "";
-                  },
-                );
-              }}
-              path={`inmemory://sdcpn/differential-equations/${differentialEquation.id}.ts`}
-              theme="vs-light"
-              options={{
-                minimap: { enabled: false },
-                scrollBeyondLastLine: false,
-                fontSize: 12,
-                lineNumbers: "off",
-                folding: true,
-                glyphMargin: false,
-                lineDecorationsWidth: 0,
-                lineNumbersMinChars: 3,
-                padding: { top: 8, bottom: 8 },
-                fixedOverflowWidgets: true,
-                readOnly: isReadOnly,
-              }}
-            />
-          </div>
-        </DisabledTooltip>
+        <CodeEditor
+          language="typescript"
+          value={differentialEquation.code}
+          height="100%"
+          onChange={(newCode) => {
+            updateDifferentialEquation(
+              differentialEquation.id,
+              (existingEquation) => {
+                existingEquation.code = newCode ?? "";
+              },
+            );
+          }}
+          path={`inmemory://sdcpn/differential-equations/${differentialEquation.id}.ts`}
+          options={{ readOnly: isReadOnly }}
+          tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
     </div>
   );
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/parameter-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/parameter-properties.tsx
index 231415dc6a8..a6f49c2b7d6 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/parameter-properties.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/parameter-properties.tsx
@@ -1,6 +1,7 @@
-import { css, cva } from "@hashintel/ds-helpers/css";
+import { css } from "@hashintel/ds-helpers/css";
 
-import { DisabledTooltip } from "../../../../components/disabled-tooltip";
+import { Input } from "../../../../components/input";
+import { UI_MESSAGES } from "../../../../constants/ui-messages";
 import type { Parameter } from "../../../../core/types/sdcpn";
 import { useIsReadOnly } from "../../../../state/use-is-read-only";
 
@@ -22,38 +23,6 @@ const fieldLabelStyle = css({
   marginBottom: "[4px]",
 });
 
-const inputStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.15)]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-  },
-  variants: {
-    isDisabled: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.02)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-    isMonospace: {
-      true: {
-        fontFamily: "[monospace]",
-      },
-      false: {},
-    },
-  },
-  defaultVariants: {
-    isDisabled: false,
-    isMonospace: false,
-  },
-});
-
 /**
  * Slugifies a string to a valid JavaScript identifier.
  * - Converts to lowercase
@@ -142,30 +111,25 @@ export const ParameterProperties: React.FC<ParameterPropertiesProps> = ({
       {/* Name field */}
       <div>
         <div className={fieldLabelStyle}>Name</div>
-        <DisabledTooltip disabled={isDisabled}>
-          <input
-            type="text"
-            value={parameter.name}
-            onChange={handleUpdateName}
-            disabled={isDisabled}
-            className={inputStyle({ isDisabled })}
-          />
-        </DisabledTooltip>
+        <Input
+          value={parameter.name}
+          onChange={handleUpdateName}
+          disabled={isDisabled}
+          tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
 
       {/* Variable Name field */}
       <div>
         <div className={fieldLabelStyle}>Variable Name</div>
-        <DisabledTooltip disabled={isDisabled}>
-          <input
-            type="text"
-            value={parameter.variableName}
-            onChange={handleUpdateVariableName}
-            onBlur={handleBlurVariableName}
-            disabled={isDisabled}
-            className={inputStyle({ isDisabled, isMonospace: true })}
-          />
-        </DisabledTooltip>
+        <Input
+          value={parameter.variableName}
+          onChange={handleUpdateVariableName}
+          onBlur={handleBlurVariableName}
+          disabled={isDisabled}
+          monospace
+          tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
 
       {/* Type selector - hidden for now as internal code relies on "real" type */}
@@ -173,15 +137,13 @@ export const ParameterProperties: React.FC<ParameterPropertiesProps> = ({
       {/* Default Value field */}
       <div>
         <div className={fieldLabelStyle}>Default Value</div>
-        <DisabledTooltip disabled={isDisabled}>
-          <input
-            type="text"
-            value={parameter.defaultValue}
-            onChange={handleUpdateDefaultValue}
-            disabled={isDisabled}
-            className={inputStyle({ isDisabled, isMonospace: true })}
-          />
-        </DisabledTooltip>
+        <Input
+          value={parameter.defaultValue}
+          onChange={handleUpdateDefaultValue}
+          disabled={isDisabled}
+          monospace
+          tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
     </div>
   );
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/place-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/place-properties.tsx
index 62d8875fcac..9fa8709d250 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/place-properties.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/place-properties.tsx
@@ -1,5 +1,5 @@
 /* eslint-disable id-length */
-import { css, cva } from "@hashintel/ds-helpers/css";
+import { css } from "@hashintel/ds-helpers/css";
 import MonacoEditor from "@monaco-editor/react";
 import { use, useEffect, useMemo, useRef, useState } from "react";
 import {
@@ -9,8 +9,11 @@ import {
   TbTrash,
 } from "react-icons/tb";
 
-import { DisabledTooltip } from "../../../../components/disabled-tooltip";
+import { Button } from "../../../../components/button";
+import { IconButton } from "../../../../components/icon-button";
+import { Input } from "../../../../components/input";
 import { Menu } from "../../../../components/menu";
+import { Select } from "../../../../components/select";
 import type { SubView } from "../../../../components/sub-view/types";
 import { FixedHeightSubViewsContainer } from "../../../../components/sub-view/vertical-sub-views-container";
 import { Switch } from "../../../../components/switch";
@@ -61,24 +64,6 @@ const headerTitleStyle = css({
   fontSize: "[16px]",
 });
 
-const deleteButtonStyle = css({
-  display: "flex",
-  alignItems: "center",
-  justifyContent: "center",
-  width: "[24px]",
-  height: "[24px]",
-  padding: "0",
-  border: "none",
-  background: "[transparent]",
-  cursor: "pointer",
-  color: "gray.60",
-  borderRadius: "md.4",
-  _hover: {
-    color: "red.60",
-    backgroundColor: "red.10",
-  },
-});
-
 const fieldLabelStyle = css({
   fontWeight: "medium",
   fontSize: "[12px]",
@@ -93,92 +78,16 @@ const fieldLabelWithTooltipStyle = css({
   alignItems: "center",
 });
 
-const inputStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-    boxSizing: "border-box",
-  },
-  variants: {
-    isReadOnly: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-    hasError: {
-      true: {
-        border: "[1px solid #ef4444]",
-      },
-      false: {
-        border: "[1px solid rgba(0, 0, 0, 0.1)]",
-      },
-    },
-  },
-  defaultVariants: {
-    isReadOnly: false,
-    hasError: false,
-  },
-});
-
 const errorMessageStyle = css({
   fontSize: "[12px]",
   color: "[#ef4444]",
   marginTop: "[4px]",
 });
 
-const selectStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-    boxSizing: "border-box",
-  },
-  variants: {
-    isReadOnly: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "pointer",
-      },
-    },
-    hasMarginBottom: {
-      true: {
-        marginBottom: "[8px]",
-      },
-      false: {},
-    },
-  },
-});
-
 const jumpButtonContainerStyle = css({
   textAlign: "right",
 });
 
-const jumpButtonStyle = css({
-  fontSize: "[12px]",
-  padding: "[4px 8px]",
-  border: "[1px solid rgba(0, 0, 0, 0.2)]",
-  borderRadius: "[4px]",
-  backgroundColor: "[white]",
-  cursor: "pointer",
-  color: "[#333]",
-  display: "inline-flex",
-  alignItems: "center",
-  gap: "[6px]",
-});
-
 const jumpIconStyle = css({
   fontSize: "[14px]",
 });
@@ -384,50 +293,48 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
         <div>
           <div className={headerContainerStyle}>
             <div className={headerTitleStyle}>Place</div>
-            <Tooltip content="Delete">
-              <button
-                type="button"
-                onClick={() => {
-                  if (
-                    // eslint-disable-next-line no-alert
-                    window.confirm(
-                      `Are you sure you want to delete "${place.name}"? All arcs connected to this place will also be removed.`,
-                    )
-                  ) {
-                    removePlace(place.id);
-                  }
-                }}
-                className={deleteButtonStyle}
-              >
-                <TbTrash size={16} />
-              </button>
-            </Tooltip>
+            <IconButton
+              aria-label="Delete"
+              variant="danger"
+              onClick={() => {
+                if (
+                  // eslint-disable-next-line no-alert
+                  window.confirm(
+                    `Are you sure you want to delete "${place.name}"? All arcs connected to this place will also be removed.`,
+                  )
+                ) {
+                  removePlace(place.id);
+                }
+              }}
+              disabled={isReadOnly}
+              tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : "Delete"}
+            >
+              <TbTrash size={16} />
+            </IconButton>
           </div>
         </div>
 
         <div>
           <div className={fieldLabelStyle}>Name</div>
-          <DisabledTooltip disabled={isReadOnly}>
-            <input
-              ref={nameInputRef}
-              type="text"
-              value={nameInputValue}
-              onChange={(event) => {
-                setNameInputValue(event.target.value);
-                // Clear error when user starts typing
-                if (nameError) {
-                  setNameError(null);
-                }
-              }}
-              onFocus={() => setIsNameInputFocused(true)}
-              onBlur={() => {
-                setIsNameInputFocused(false);
-                handleNameBlur();
-              }}
-              disabled={isReadOnly}
-              className={inputStyle({ isReadOnly, hasError: !!nameError })}
-            />
-          </DisabledTooltip>
+          <Input
+            ref={nameInputRef}
+            value={nameInputValue}
+            onChange={(event) => {
+              setNameInputValue(event.target.value);
+              // Clear error when user starts typing
+              if (nameError) {
+                setNameError(null);
+              }
+            }}
+            onFocus={() => setIsNameInputFocused(true)}
+            onBlur={() => {
+              setIsNameInputFocused(false);
+              handleNameBlur();
+            }}
+            disabled={isReadOnly}
+            hasError={!!nameError}
+            tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+          />
           {nameError && <div className={errorMessageStyle}>{nameError}</div>}
         </div>
 
@@ -442,47 +349,41 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
               } Tokens in places don't have to carry data, but they need one to enable dynamics (token data changing over time when in a place).`}
             />
           </div>
-          <DisabledTooltip disabled={isReadOnly}>
-            <select
-              value={place.colorId ?? ""}
-              onChange={(event) => {
-                const value = event.target.value;
-                const newType = value === "" ? null : value;
-                updatePlace(place.id, (existingPlace) => {
-                  existingPlace.colorId = newType;
-                  // Disable dynamics if type is being set to null
-                  if (newType === null && existingPlace.dynamicsEnabled) {
-                    existingPlace.dynamicsEnabled = false;
-                  }
-                });
-              }}
-              disabled={isReadOnly}
-              className={selectStyle({
-                isReadOnly,
-                hasMarginBottom: !!place.colorId,
-              })}
-            >
-              <option value="">None</option>
-              {types.map((type) => (
-                <option key={type.id} value={type.id}>
-                  {type.name}
-                </option>
-              ))}
-            </select>
-          </DisabledTooltip>
+          <Select
+            value={place.colorId ?? ""}
+            onChange={(event) => {
+              const value = event.target.value;
+              const newType = value === "" ? null : value;
+              updatePlace(place.id, (existingPlace) => {
+                existingPlace.colorId = newType;
+                // Disable dynamics if type is being set to null
+                if (newType === null && existingPlace.dynamicsEnabled) {
+                  existingPlace.dynamicsEnabled = false;
+                }
+              });
+            }}
+            disabled={isReadOnly}
+            style={place.colorId ? { marginBottom: "8px" } : undefined}
+            tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+          >
+            <option value="">None</option>
+            {types.map((type) => (
+              <option key={type.id} value={type.id}>
+                {type.name}
+              </option>
+            ))}
+          </Select>
 
           {place.colorId && (
             <div className={jumpButtonContainerStyle}>
-              <button
-                type="button"
+              <Button
                 onClick={() => {
                   setSelectedResourceId(place.colorId);
                 }}
-                className={jumpButtonStyle}
               >
                 Jump to Type
                 <TbArrowRight className={jumpIconStyle} />
-              </button>
+              </Button>
             </div>
           )}
         </div>
@@ -490,17 +391,22 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
         <div className={sectionContainerStyle}>
           <div className={switchRowStyle}>
             <div className={switchContainerStyle}>
-              <DisabledTooltip disabled={isReadOnly}>
-                <Switch
-                  checked={!!place.colorId && place.dynamicsEnabled}
-                  disabled={isReadOnly || place.colorId === null}
-                  onCheckedChange={(checked) => {
-                    updatePlace(place.id, (existingPlace) => {
-                      existingPlace.dynamicsEnabled = checked;
-                    });
-                  }}
-                />
-              </DisabledTooltip>
+              <Switch
+                checked={!!place.colorId && place.dynamicsEnabled}
+                disabled={isReadOnly || place.colorId === null}
+                tooltip={
+                  isReadOnly
+                    ? UI_MESSAGES.READ_ONLY_MODE
+                    : place.colorId === null
+                      ? UI_MESSAGES.DYNAMICS_REQUIRES_TYPE
+                      : undefined
+                }
+                onCheckedChange={(checked) => {
+                  updatePlace(place.id, (existingPlace) => {
+                    existingPlace.dynamicsEnabled = checked;
+                  });
+                }}
+              />
             </div>
             <div className={fieldLabelWithTooltipStyle}>
               Dynamics
@@ -523,40 +429,37 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
           availableDiffEqs.length > 0 && (
             <div className={diffEqContainerStyle}>
               <div className={fieldLabelStyle}>Differential Equation</div>
-              <DisabledTooltip disabled={isReadOnly}>
-                <select
-                  value={place.differentialEquationId ?? undefined}
-                  onChange={(event) => {
-                    const value = event.target.value;
-
-                    updatePlace(place.id, (existingPlace) => {
-                      existingPlace.differentialEquationId = value || null;
-                    });
-                  }}
-                  disabled={isReadOnly}
-                  className={selectStyle({ isReadOnly, hasMarginBottom: true })}
-                >
-                  <option value="">None</option>
-                  {availableDiffEqs.map((eq) => (
-                    <option key={eq.id} value={eq.id}>
-                      {eq.name}
-                    </option>
-                  ))}
-                </select>
-              </DisabledTooltip>
+              <Select
+                value={place.differentialEquationId ?? undefined}
+                onChange={(event) => {
+                  const value = event.target.value;
+
+                  updatePlace(place.id, (existingPlace) => {
+                    existingPlace.differentialEquationId = value || null;
+                  });
+                }}
+                disabled={isReadOnly}
+                style={{ marginBottom: "8px" }}
+                tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+              >
+                <option value="">None</option>
+                {availableDiffEqs.map((eq) => (
+                  <option key={eq.id} value={eq.id}>
+                    {eq.name}
+                  </option>
+                ))}
+              </Select>
 
               {place.differentialEquationId && (
                 <div className={jumpButtonContainerStyle}>
-                  <button
-                    type="button"
+                  <Button
                     onClick={() => {
                       setSelectedResourceId(place.differentialEquationId);
                     }}
-                    className={jumpButtonStyle}
                   >
                     Jump to Differential Equation
                     <TbArrowRight className={jumpIconStyle} />
-                  </button>
+                  </Button>
                 </div>
               )}
             </div>
@@ -569,6 +472,8 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
               <div className={switchContainerStyle}>
                 <Switch
                   checked={place.visualizerCode !== undefined}
+                  disabled={isReadOnly}
+                  tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
                   onCheckedChange={(checked) => {
                     if (checked) {
                       // Turning on: use saved code if available, otherwise default
@@ -627,7 +532,10 @@ export const PlaceProperties: React.FC<PlacePropertiesProps> = ({
                   {
                     id: "generate-ai",
                     label: (
-                      <Tooltip content={UI_MESSAGES.AI_FEATURE_COMING_SOON}>
+                      <Tooltip
+                        content={UI_MESSAGES.AI_FEATURE_COMING_SOON}
+                        display="inline"
+                      >
                         <div className={aiMenuItemStyle}>
                           <TbSparkles className={aiIconStyle} />
                           Generate with AI
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/sortable-arc-item.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/sortable-arc-item.tsx
index 55c947d3b4b..d68cb37ac3c 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/sortable-arc-item.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/sortable-arc-item.tsx
@@ -4,6 +4,8 @@ import { css, cva } from "@hashintel/ds-helpers/css";
 import { MdDragIndicator } from "react-icons/md";
 import { TbTrash } from "react-icons/tb";
 
+import { IconButton } from "../../../../components/icon-button";
+import { NumberInput } from "../../../../components/number-input";
 import { FEATURE_FLAGS } from "../../../../feature-flags";
 
 const containerStyle = css({
@@ -60,46 +62,10 @@ const weightLabelStyle = css({
   fontWeight: "medium",
 });
 
-const weightInputStyle = cva({
-  base: {
-    width: "[60px]",
-    fontSize: "[14px]",
-    padding: "[4px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    boxSizing: "border-box",
-  },
-  variants: {
-    isDisabled: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-  },
-});
-
-const deleteButtonStyle = css({
-  display: "flex",
-  alignItems: "center",
-  justifyContent: "center",
-  width: "[24px]",
-  height: "[24px]",
-  padding: "0",
-  border: "none",
-  background: "[transparent]",
-  cursor: "pointer",
-  color: "gray.60",
-  flexShrink: 0,
-  borderRadius: "md.4",
-  _hover: {
-    color: "red.60",
-    backgroundColor: "red.10",
-  },
+const weightInputStyle = css({
+  width: "[60px]",
+  fontSize: "[14px]",
+  padding: "[4px 8px]",
 });
 
 /**
@@ -110,6 +76,8 @@ interface SortableArcItemProps {
   placeName: string;
   weight: number;
   disabled?: boolean;
+  /** Tooltip to show when disabled (e.g., for read-only mode) */
+  tooltip?: string;
   onWeightChange: (weight: number) => void;
   onDelete?: () => void;
 }
@@ -119,6 +87,7 @@ export const SortableArcItem: React.FC<SortableArcItemProps> = ({
   placeName,
   weight,
   disabled = false,
+  tooltip,
   onWeightChange,
   onDelete,
 }) => {
@@ -151,25 +120,25 @@ export const SortableArcItem: React.FC<SortableArcItemProps> = ({
       <div className={placeNameStyle}>{placeName}</div>
       <div className={weightContainerStyle}>
         <span className={weightLabelStyle}>weight</span>
-        <input
-          type="number"
-          min="1"
-          step="1"
+        <NumberInput
+          min={1}
+          step={1}
           value={weight}
           disabled={disabled}
+          tooltip={tooltip}
           onChange={(event) => {
             const newWeight = Number.parseInt(event.target.value, 10);
             if (!Number.isNaN(newWeight) && newWeight >= 1) {
               onWeightChange(newWeight);
             }
           }}
-          className={weightInputStyle({ isDisabled: disabled })}
+          className={weightInputStyle}
         />
       </div>
       {onDelete && !disabled && (
-        <button type="button" onClick={onDelete} className={deleteButtonStyle}>
+        <IconButton aria-label="Delete" variant="danger" onClick={onDelete}>
           <TbTrash size={16} />
-        </button>
+        </IconButton>
       )}
     </div>
   );
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/transition-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/transition-properties.tsx
index 83f4ce1dd97..22201b754ee 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/transition-properties.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/transition-properties.tsx
@@ -15,12 +15,13 @@ import {
   sortableKeyboardCoordinates,
   verticalListSortingStrategy,
 } from "@dnd-kit/sortable";
-import { css, cva } from "@hashintel/ds-helpers/css";
-import MonacoEditor from "@monaco-editor/react";
+import { css } from "@hashintel/ds-helpers/css";
 import { use } from "react";
 import { TbDotsVertical, TbSparkles, TbTrash } from "react-icons/tb";
 
-import { DisabledTooltip } from "../../../../components/disabled-tooltip";
+import { CodeEditor } from "../../../../components/code-editor";
+import { IconButton } from "../../../../components/icon-button";
+import { Input } from "../../../../components/input";
 import { Menu } from "../../../../components/menu";
 import { SegmentGroup } from "../../../../components/segment-group";
 import { InfoIconTooltip, Tooltip } from "../../../../components/tooltip";
@@ -53,53 +54,12 @@ const headerTitleStyle = css({
   fontSize: "[16px]",
 });
 
-const deleteButtonStyle = css({
-  display: "flex",
-  alignItems: "center",
-  justifyContent: "center",
-  width: "[24px]",
-  height: "[24px]",
-  padding: "0",
-  border: "none",
-  background: "[transparent]",
-  cursor: "pointer",
-  color: "gray.60",
-  borderRadius: "md.4",
-  _hover: {
-    color: "red.60",
-    backgroundColor: "red.10",
-  },
-});
-
 const fieldLabelStyle = css({
   fontWeight: "medium",
   fontSize: "[12px]",
   marginBottom: "[4px]",
 });
 
-const inputStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-    boxSizing: "border-box",
-  },
-  variants: {
-    isReadOnly: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-  },
-});
-
 const sectionContainerStyle = css({
   marginTop: "[20px]",
 });
@@ -115,22 +75,8 @@ const arcListContainerStyle = css({
   overflow: "hidden",
 });
 
-const segmentGroupWrapperStyle = cva({
-  base: {
-    marginTop: "[8px]",
-  },
-  variants: {
-    isReadOnly: {
-      true: {
-        opacity: "[0.6]",
-        pointerEvents: "none",
-      },
-      false: {
-        opacity: "[1]",
-        pointerEvents: "auto",
-      },
-    },
-  },
+const segmentGroupContainerStyle = css({
+  marginTop: "[8px]",
 });
 
 const infoBoxStyle = css({
@@ -166,29 +112,6 @@ const menuButtonStyle = css({
   color: "[rgba(0, 0, 0, 0.6)]",
 });
 
-const editorContainerStyle = cva({
-  base: {
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    overflow: "hidden",
-  },
-  variants: {
-    size: {
-      lambda: { height: "[340px]" },
-      kernel: { height: "[400px]" },
-    },
-    isReadOnly: {
-      true: {
-        filter: "[grayscale(20%) brightness(98%)]",
-        pointerEvents: "none",
-      },
-      false: {
-        pointerEvents: "auto",
-      },
-    },
-  },
-});
-
 const aiMenuItemStyle = css({
   display: "flex",
   alignItems: "center",
@@ -342,42 +265,39 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
       <div>
         <div className={headerContainerStyle}>
           <div className={headerTitleStyle}>Transition</div>
-          <Tooltip content="Delete">
-            <button
-              type="button"
-              onClick={() => {
-                if (
-                  // eslint-disable-next-line no-alert
-                  window.confirm(
-                    `Are you sure you want to delete "${transition.name}"? All arcs connected to this transition will also be removed.`,
-                  )
-                ) {
-                  removeTransition(transition.id);
-                }
-              }}
-              className={deleteButtonStyle}
-            >
-              <TbTrash size={16} />
-            </button>
-          </Tooltip>
+          <IconButton
+            aria-label="Delete"
+            variant="danger"
+            onClick={() => {
+              if (
+                // eslint-disable-next-line no-alert
+                window.confirm(
+                  `Are you sure you want to delete "${transition.name}"? All arcs connected to this transition will also be removed.`,
+                )
+              ) {
+                removeTransition(transition.id);
+              }
+            }}
+            disabled={isReadOnly}
+            tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : "Delete"}
+          >
+            <TbTrash size={16} />
+          </IconButton>
         </div>
       </div>
 
       <div>
         <div className={fieldLabelStyle}>Name</div>
-        <DisabledTooltip disabled={isReadOnly}>
-          <input
-            type="text"
-            value={transition.name}
-            onChange={(event) => {
-              updateTransition(transition.id, (existingTransition) => {
-                existingTransition.name = event.target.value;
-              });
-            }}
-            disabled={isReadOnly}
-            className={inputStyle({ isReadOnly })}
-          />
-        </DisabledTooltip>
+        <Input
+          value={transition.name}
+          onChange={(event) => {
+            updateTransition(transition.id, (existingTransition) => {
+              existingTransition.name = event.target.value;
+            });
+          }}
+          disabled={isReadOnly}
+          tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
 
       <div className={sectionContainerStyle}>
@@ -408,6 +328,9 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
                       placeName={place?.name ?? arc.placeId}
                       weight={arc.weight}
                       disabled={isReadOnly}
+                      tooltip={
+                        isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined
+                      }
                       onWeightChange={(weight) => {
                         onArcWeightUpdate(
                           transition.id,
@@ -454,6 +377,9 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
                       placeName={place?.name ?? arc.placeId}
                       weight={arc.weight}
                       disabled={isReadOnly}
+                      tooltip={
+                        isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined
+                      }
                       onWeightChange={(weight) => {
                         onArcWeightUpdate(
                           transition.id,
@@ -477,24 +403,24 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
           Firing time
           <InfoIconTooltip tooltip="Define the rate at or conditions under which this will transition will fire, optionally based on each set of input tokens' data (where input tokens have types)." />
         </div>
-        <DisabledTooltip disabled={isReadOnly}>
-          <div className={segmentGroupWrapperStyle({ isReadOnly })}>
-            <SegmentGroup
-              value={transition.lambdaType}
-              options={[
-                { value: "predicate", label: "Predicate" },
-                { value: "stochastic", label: "Stochastic Rate" },
-              ]}
-              onChange={(value) => {
-                updateTransition(transition.id, (existingTransition) => {
-                  existingTransition.lambdaType = value as
-                    | "predicate"
-                    | "stochastic";
-                });
-              }}
-            />
-          </div>
-        </DisabledTooltip>
+        <div className={segmentGroupContainerStyle}>
+          <SegmentGroup
+            value={transition.lambdaType}
+            options={[
+              { value: "predicate", label: "Predicate" },
+              { value: "stochastic", label: "Stochastic Rate" },
+            ]}
+            onChange={(value) => {
+              updateTransition(transition.id, (existingTransition) => {
+                existingTransition.lambdaType = value as
+                  | "predicate"
+                  | "stochastic";
+              });
+            }}
+            disabled={isReadOnly}
+            tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+          />
+        </div>
       </div>
 
       <div>
@@ -534,7 +460,10 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
                 {
                   id: "generate-ai",
                   label: (
-                    <Tooltip content={UI_MESSAGES.AI_FEATURE_COMING_SOON}>
+                    <Tooltip
+                      content={UI_MESSAGES.AI_FEATURE_COMING_SOON}
+                      display="inline"
+                    >
                       <div className={aiMenuItemStyle}>
                         <TbSparkles className={aiIconStyle} />
                         Generate with AI
@@ -550,37 +479,22 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
             />
           )}
         </div>
-        <DisabledTooltip disabled={isReadOnly}>
-          <div className={editorContainerStyle({ isReadOnly, size: "lambda" })}>
-            <MonacoEditor
-              key={`lambda-${transition.lambdaType}-${transition.inputArcs
-                .map((a) => `${a.placeId}:${a.weight}`)
-                .join("-")}`}
-              language="typescript"
-              value={transition.lambdaCode || ""}
-              path={`inmemory://sdcpn/transitions/${transition.id}/lambda.ts`}
-              onChange={(value) => {
-                updateTransition(transition.id, (existingTransition) => {
-                  existingTransition.lambdaCode = value ?? "";
-                });
-              }}
-              theme="vs-light"
-              options={{
-                minimap: { enabled: false },
-                scrollBeyondLastLine: false,
-                fontSize: 12,
-                lineNumbers: "off",
-                folding: true,
-                glyphMargin: false,
-                lineDecorationsWidth: 0,
-                lineNumbersMinChars: 3,
-                padding: { top: 8, bottom: 8 },
-                readOnly: isReadOnly,
-                fixedOverflowWidgets: true,
-              }}
-            />
-          </div>
-        </DisabledTooltip>
+        <CodeEditor
+          key={`lambda-${transition.lambdaType}-${transition.inputArcs
+            .map((a) => `${a.placeId}:${a.weight}`)
+            .join("-")}`}
+          language="typescript"
+          value={transition.lambdaCode || ""}
+          path={`inmemory://sdcpn/transitions/${transition.id}/lambda.ts`}
+          height={340}
+          onChange={(value) => {
+            updateTransition(transition.id, (existingTransition) => {
+              existingTransition.lambdaCode = value ?? "";
+            });
+          }}
+          options={{ readOnly: isReadOnly }}
+          tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
 
       {/* Only show Transition Results if at least one output place has a type */}
@@ -649,7 +563,10 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
                   {
                     id: "generate-ai",
                     label: (
-                      <Tooltip content={UI_MESSAGES.AI_FEATURE_COMING_SOON}>
+                      <Tooltip
+                        content={UI_MESSAGES.AI_FEATURE_COMING_SOON}
+                        display="inline"
+                      >
                         <div className={aiMenuItemStyle}>
                           <TbSparkles className={aiIconStyle} />
                           Generate with AI
@@ -665,41 +582,24 @@ export const TransitionProperties: React.FC<TransitionPropertiesProps> = ({
               />
             )}
           </div>
-          <DisabledTooltip disabled={isReadOnly}>
-            <div
-              className={editorContainerStyle({ isReadOnly, size: "kernel" })}
-            >
-              <MonacoEditor
-                key={`kernel-${transition.inputArcs
-                  .map((a) => `${a.placeId}:${a.weight}`)
-                  .join("-")}-${transition.outputArcs
-                  .map((a) => `${a.placeId}:${a.weight}`)
-                  .join("-")}`}
-                language="typescript"
-                value={transition.transitionKernelCode || ""}
-                path={`inmemory://sdcpn/transitions/${transition.id}/transition-kernel.ts`}
-                onChange={(value) => {
-                  updateTransition(transition.id, (existingTransition) => {
-                    existingTransition.transitionKernelCode = value ?? "";
-                  });
-                }}
-                theme="vs-light"
-                options={{
-                  minimap: { enabled: false },
-                  scrollBeyondLastLine: false,
-                  fontSize: 12,
-                  lineNumbers: "off",
-                  folding: true,
-                  glyphMargin: false,
-                  lineDecorationsWidth: 0,
-                  lineNumbersMinChars: 3,
-                  padding: { top: 8, bottom: 8 },
-                  readOnly: isReadOnly,
-                  fixedOverflowWidgets: true,
-                }}
-              />
-            </div>
-          </DisabledTooltip>
+          <CodeEditor
+            key={`kernel-${transition.inputArcs
+              .map((a) => `${a.placeId}:${a.weight}`)
+              .join("-")}-${transition.outputArcs
+              .map((a) => `${a.placeId}:${a.weight}`)
+              .join("-")}`}
+            language="typescript"
+            value={transition.transitionKernelCode || ""}
+            path={`inmemory://sdcpn/transitions/${transition.id}/transition-kernel.ts`}
+            height={400}
+            onChange={(value) => {
+              updateTransition(transition.id, (existingTransition) => {
+                existingTransition.transitionKernelCode = value ?? "";
+              });
+            }}
+            options={{ readOnly: isReadOnly }}
+            tooltip={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+          />
         </div>
       ) : (
         <div className={noOutputTypesBoxStyle}>
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/type-properties.tsx b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/type-properties.tsx
index aa96b289689..841431dc33f 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/type-properties.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/panels/PropertiesPanel/type-properties.tsx
@@ -2,7 +2,10 @@ import { css, cva } from "@hashintel/ds-helpers/css";
 import { useState } from "react";
 import { v4 as uuidv4 } from "uuid";
 
-import { DisabledTooltip } from "../../../../components/disabled-tooltip";
+import { Button } from "../../../../components/button";
+import { Input } from "../../../../components/input";
+import { Tooltip } from "../../../../components/tooltip";
+import { UI_MESSAGES } from "../../../../constants/ui-messages";
 import type { Color } from "../../../../core/types/sdcpn";
 import { useIsReadOnly } from "../../../../state/use-is-read-only";
 import { ColorSelect } from "./color-select";
@@ -25,29 +28,6 @@ const fieldLabelStyle = css({
   marginBottom: "[4px]",
 });
 
-const inputStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-    boxSizing: "border-box",
-  },
-  variants: {
-    isDisabled: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-  },
-});
-
 const dimensionsHeaderStyle = css({
   display: "flex",
   alignItems: "center",
@@ -71,22 +51,16 @@ const addDimensionButtonStyle = cva({
   base: {
     fontSize: "[16px]",
     padding: "[2px 8px]",
-    borderRadius: "[4px]",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-    fontWeight: "semibold",
-    cursor: "pointer",
   },
   variants: {
     isDisabled: {
       true: {
         backgroundColor: "[rgba(0, 0, 0, 0.05)]",
         color: "[#999]",
-        cursor: "not-allowed",
       },
       false: {
         backgroundColor: "[rgba(59, 130, 246, 0.1)]",
         color: "[#3b82f6]",
-        cursor: "pointer",
       },
     },
   },
@@ -179,54 +153,38 @@ const indexChipStyle = css({
   flexShrink: 0,
 });
 
-const dimensionNameInputStyle = cva({
+const dimensionNameInputStyle = css({
+  fontSize: "[13px]",
+  padding: "[5px 8px]",
+  flex: "1",
+});
+
+const deleteDimensionButtonStyle = cva({
   base: {
-    fontSize: "[13px]",
-    padding: "[5px 8px]",
-    border: "[1px solid rgba(0, 0, 0, 0.15)]",
+    fontSize: "[16px]",
+    width: "[28px]",
+    height: "[28px]",
     borderRadius: "[3px]",
-    flex: "1",
+    fontWeight: "semibold",
+    lineHeight: "[1]",
+    flexShrink: 0,
   },
   variants: {
     isDisabled: {
       true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.02)]",
-        cursor: "not-allowed",
+        border: "[1px solid rgba(0, 0, 0, 0.1)]",
+        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
+        color: "[#999]",
       },
       false: {
-        backgroundColor: "[white]",
-        cursor: "text",
+        border: "[1px solid rgba(239, 68, 68, 0.2)]",
+        backgroundColor: "[rgba(239, 68, 68, 0.08)]",
+        color: "[#ef4444]",
       },
     },
   },
 });
 
-const deleteDimensionButtonStyle = css({
-  fontSize: "[16px]",
-  width: "[28px]",
-  height: "[28px]",
-  borderRadius: "[3px]",
-  border: "[1px solid rgba(239, 68, 68, 0.2)]",
-  backgroundColor: "[rgba(239, 68, 68, 0.08)]",
-  color: "[#ef4444]",
-  cursor: "pointer",
-  fontWeight: "semibold",
-  lineHeight: "[1]",
-  transition: "[all 0.15s ease]",
-  flexShrink: 0,
-  display: "flex",
-  alignItems: "center",
-  justifyContent: "center",
-  _hover: {
-    backgroundColor: "[rgba(239, 68, 68, 0.15)]",
-  },
-  _disabled: {
-    backgroundColor: "[rgba(0, 0, 0, 0.02)]",
-    color: "[#ccc]",
-    cursor: "not-allowed",
-  },
-});
-
 // --- Helpers ---
 
 /**
@@ -403,24 +361,21 @@ export const TypeProperties: React.FC<TypePropertiesProps> = ({
 
       <div>
         <div className={fieldLabelStyle}>Name</div>
-        <DisabledTooltip disabled={isDisabled}>
-          <input
-            type="text"
-            value={type.name}
-            onChange={(event) => {
-              updateType(type.id, (existingType) => {
-                existingType.name = event.target.value;
-              });
-            }}
-            disabled={isDisabled}
-            className={inputStyle({ isDisabled })}
-          />
-        </DisabledTooltip>
+        <Input
+          value={type.name}
+          onChange={(event) => {
+            updateType(type.id, (existingType) => {
+              existingType.name = event.target.value;
+            });
+          }}
+          disabled={isDisabled}
+          tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+        />
       </div>
 
       <div>
         <div className={fieldLabelStyle}>Color</div>
-        <DisabledTooltip disabled={isDisabled}>
+        <Tooltip content={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}>
           <ColorSelect
             value={type.displayColor}
             onChange={(color) => {
@@ -430,7 +385,7 @@ export const TypeProperties: React.FC<TypePropertiesProps> = ({
             }}
             disabled={isDisabled}
           />
-        </DisabledTooltip>
+        </Tooltip>
       </div>
 
       {/* Dimensions Section - Editable with drag-to-reorder */}
@@ -440,17 +395,15 @@ export const TypeProperties: React.FC<TypePropertiesProps> = ({
             Dimensions
             <span className={dimensionsHintStyle}>(order matters)</span>
           </div>
-          <DisabledTooltip disabled={isDisabled}>
-            <button
-              type="button"
-              onClick={handleAddElement}
-              disabled={isDisabled}
-              className={addDimensionButtonStyle({ isDisabled })}
-              aria-label="Add dimension"
-            >
-              +
-            </button>
-          </DisabledTooltip>
+          <Button
+            onClick={handleAddElement}
+            disabled={isDisabled}
+            className={addDimensionButtonStyle({ isDisabled })}
+            aria-label="Add dimension"
+            tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+          >
+            +
+          </Button>
         </div>
 
         {type.elements.length === 0 ? (
@@ -489,42 +442,40 @@ export const TypeProperties: React.FC<TypePropertiesProps> = ({
                 <div className={indexChipStyle}>{index}</div>
 
                 {/* Name input */}
-                <DisabledTooltip disabled={isDisabled}>
-                  <input
-                    type="text"
-                    value={element.name}
-                    onChange={(event) => {
-                      handleUpdateElementName(
-                        element.elementId,
-                        event.target.value,
-                      );
-                    }}
-                    onBlur={(event) => {
-                      handleBlurElementName(
-                        element.elementId,
-                        event.target.value,
-                      );
-                    }}
-                    disabled={isDisabled}
-                    placeholder="dimension_name"
-                    className={dimensionNameInputStyle({ isDisabled })}
-                  />
-                </DisabledTooltip>
+                <Input
+                  value={element.name}
+                  onChange={(event) => {
+                    handleUpdateElementName(
+                      element.elementId,
+                      event.target.value,
+                    );
+                  }}
+                  onBlur={(event) => {
+                    handleBlurElementName(
+                      element.elementId,
+                      event.target.value,
+                    );
+                  }}
+                  disabled={isDisabled}
+                  placeholder="dimension_name"
+                  className={dimensionNameInputStyle}
+                  tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+                />
 
                 {/* Delete button */}
-                <DisabledTooltip disabled={isDisabled}>
-                  <button
-                    type="button"
-                    onClick={() => {
-                      handleDeleteElement(element.elementId, element.name);
-                    }}
-                    disabled={isDisabled || type.elements.length === 1}
-                    className={deleteDimensionButtonStyle}
-                    aria-label={`Delete dimension ${element.name}`}
-                  >
-                    ×
-                  </button>
-                </DisabledTooltip>
+                <Button
+                  onClick={() => {
+                    handleDeleteElement(element.elementId, element.name);
+                  }}
+                  disabled={isDisabled || type.elements.length === 1}
+                  className={deleteDimensionButtonStyle({
+                    isDisabled: isDisabled || type.elements.length === 1,
+                  })}
+                  aria-label={`Delete dimension ${element.name}`}
+                  tooltip={isDisabled ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+                >
+                  ×
+                </Button>
               </div>
             ))}
           </div>
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/subviews/differential-equations-list.tsx b/libs/@hashintel/petrinaut/src/views/Editor/subviews/differential-equations-list.tsx
index e9f152fc316..f6963007cd9 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/subviews/differential-equations-list.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/subviews/differential-equations-list.tsx
@@ -3,10 +3,12 @@ import { use } from "react";
 import { v4 as uuidv4 } from "uuid";
 
 import type { SubView } from "../../../components/sub-view/types";
+import { Tooltip } from "../../../components/tooltip";
+import { UI_MESSAGES } from "../../../constants/ui-messages";
 import { DEFAULT_DIFFERENTIAL_EQUATION_CODE } from "../../../core/default-codes";
-import { SimulationContext } from "../../../simulation/context";
 import { EditorContext } from "../../../state/editor-context";
 import { SDCPNContext } from "../../../state/sdcpn-context";
+import { useIsReadOnly } from "../../../state/use-is-read-only";
 
 const listContainerStyle = css({
   display: "flex",
@@ -120,10 +122,7 @@ const DifferentialEquationsSectionContent: React.FC = () => {
 
   const { selectedResourceId, setSelectedResourceId } = use(EditorContext);
 
-  // Check if simulation is running or paused
-  const { state: simulationState } = use(SimulationContext);
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
+  const isReadOnly = useIsReadOnly();
 
   return (
     <div className={listContainerStyle}>
@@ -155,24 +154,29 @@ const DifferentialEquationsSectionContent: React.FC = () => {
             <div className={equationNameContainerStyle}>
               <span>{eq.name}</span>
             </div>
-            <button
-              type="button"
-              disabled={isSimulationActive}
-              onClick={() => {
-                if (
-                  // eslint-disable-next-line no-alert
-                  window.confirm(
-                    `Delete equation "${eq.name}"? Any places referencing this equation will have their differential equation reset.`,
-                  )
-                ) {
-                  removeDifferentialEquation(eq.id);
-                }
-              }}
-              className={deleteButtonStyle}
-              aria-label={`Delete equation ${eq.name}`}
+            <Tooltip
+              content={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+              display="inline"
             >
-              ×
-            </button>
+              <button
+                type="button"
+                disabled={isReadOnly}
+                onClick={() => {
+                  if (
+                    // eslint-disable-next-line no-alert
+                    window.confirm(
+                      `Delete equation "${eq.name}"? Any places referencing this equation will have their differential equation reset.`,
+                    )
+                  ) {
+                    removeDifferentialEquation(eq.id);
+                  }
+                }}
+                className={deleteButtonStyle}
+                aria-label={`Delete equation ${eq.name}`}
+              >
+                ×
+              </button>
+            </Tooltip>
           </div>
         );
       })}
@@ -193,31 +197,33 @@ const DifferentialEquationsSectionHeaderAction: React.FC = () => {
   } = use(SDCPNContext);
   const { setSelectedResourceId } = use(EditorContext);
 
-  // Check if simulation is running or paused
-  const { state: simulationState } = use(SimulationContext);
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
+  const isReadOnly = useIsReadOnly();
 
   return (
-    <button
-      type="button"
-      disabled={isSimulationActive}
-      onClick={() => {
-        const name = `Equation ${differentialEquations.length + 1}`;
-        const id = uuidv4();
-        addDifferentialEquation({
-          id,
-          name,
-          colorId: types.length > 0 ? types[0]!.id : "",
-          code: DEFAULT_DIFFERENTIAL_EQUATION_CODE,
-        });
-        setSelectedResourceId(id);
-      }}
-      className={addButtonStyle}
-      aria-label="Add differential equation"
+    <Tooltip
+      content={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+      display="inline"
     >
-      +
-    </button>
+      <button
+        type="button"
+        disabled={isReadOnly}
+        onClick={() => {
+          const name = `Equation ${differentialEquations.length + 1}`;
+          const id = uuidv4();
+          addDifferentialEquation({
+            id,
+            name,
+            colorId: types.length > 0 ? types[0]!.id : "",
+            code: DEFAULT_DIFFERENTIAL_EQUATION_CODE,
+          });
+          setSelectedResourceId(id);
+        }}
+        className={addButtonStyle}
+        aria-label="Add differential equation"
+      >
+        +
+      </button>
+    </Tooltip>
   );
 };
 
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/subviews/parameters-list.tsx b/libs/@hashintel/petrinaut/src/views/Editor/subviews/parameters-list.tsx
index 7de447c256a..1fbf6f543eb 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/subviews/parameters-list.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/subviews/parameters-list.tsx
@@ -3,9 +3,12 @@ import { use } from "react";
 import { v4 as uuidv4 } from "uuid";
 
 import type { SubView } from "../../../components/sub-view/types";
+import { Tooltip } from "../../../components/tooltip";
+import { UI_MESSAGES } from "../../../constants/ui-messages";
 import { SimulationContext } from "../../../simulation/context";
 import { EditorContext } from "../../../state/editor-context";
 import { SDCPNContext } from "../../../state/sdcpn-context";
+import { useIsReadOnly } from "../../../state/use-is-read-only";
 
 const addButtonStyle = css({
   display: "flex",
@@ -138,12 +141,9 @@ const ParametersHeaderAction: React.FC = () => {
     petriNetDefinition: { parameters },
     addParameter,
   } = use(SDCPNContext);
-  const { globalMode, setSelectedResourceId } = use(EditorContext);
-  const { state: simulationState } = use(SimulationContext);
+  const { setSelectedResourceId } = use(EditorContext);
 
-  const isSimulationMode = globalMode === "simulate";
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
+  const isReadOnly = useIsReadOnly();
 
   const handleAddParameter = () => {
     const name = `param${parameters.length + 1}`;
@@ -158,21 +158,21 @@ const ParametersHeaderAction: React.FC = () => {
     setSelectedResourceId(id);
   };
 
-  // Don't show add button in simulation mode
-  if (isSimulationMode) {
-    return null;
-  }
-
   return (
-    <button
-      type="button"
-      disabled={isSimulationActive}
-      onClick={handleAddParameter}
-      className={addButtonStyle}
-      aria-label="Add parameter"
+    <Tooltip
+      content={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+      display="inline"
     >
-      +
-    </button>
+      <button
+        type="button"
+        disabled={isReadOnly}
+        onClick={handleAddParameter}
+        className={addButtonStyle}
+        aria-label="Add parameter"
+      >
+        +
+      </button>
+    </Tooltip>
   );
 };
 
@@ -192,14 +192,11 @@ const ParametersList: React.FC = () => {
     setParameterValue,
   } = use(SimulationContext);
 
+  const isReadOnly = useIsReadOnly();
   const isSimulationNotRun =
     globalMode === "simulate" && simulationState === "NotRun";
   const isSimulationMode = globalMode === "simulate";
 
-  // Check if simulation is running or paused
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
-
   return (
     <div>
       <div className={listContainerStyle}>
@@ -250,22 +247,29 @@ const ParametersList: React.FC = () => {
                     className={inputStyle}
                   />
                 ) : (
-                  <button
-                    type="button"
-                    disabled={isSimulationActive}
-                    onClick={() => {
-                      if (
-                        // eslint-disable-next-line no-alert
-                        window.confirm(`Delete parameter "${param.name}"?`)
-                      ) {
-                        removeParameter(param.id);
-                      }
-                    }}
-                    className={deleteButtonStyle}
-                    aria-label={`Delete ${param.name}`}
+                  <Tooltip
+                    content={
+                      isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined
+                    }
+                    display="inline"
                   >
-                    ×
-                  </button>
+                    <button
+                      type="button"
+                      disabled={isReadOnly}
+                      onClick={() => {
+                        if (
+                          // eslint-disable-next-line no-alert
+                          window.confirm(`Delete parameter "${param.name}"?`)
+                        ) {
+                          removeParameter(param.id);
+                        }
+                      }}
+                      className={deleteButtonStyle}
+                      aria-label={`Delete ${param.name}`}
+                    >
+                      ×
+                    </button>
+                  </Tooltip>
                 )}
               </div>
             </div>
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/subviews/place-initial-state.tsx b/libs/@hashintel/petrinaut/src/views/Editor/subviews/place-initial-state.tsx
index 60f71c98329..eaaf74ae49b 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/subviews/place-initial-state.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/subviews/place-initial-state.tsx
@@ -1,38 +1,14 @@
-import { css, cva } from "@hashintel/ds-helpers/css";
+import { css } from "@hashintel/ds-helpers/css";
 import { use } from "react";
 import { TbTrash } from "react-icons/tb";
 
+import { NumberInput } from "../../../components/number-input";
 import type { SubView } from "../../../components/sub-view/types";
+import { UI_MESSAGES } from "../../../constants/ui-messages";
 import { SimulationContext } from "../../../simulation/context";
 import { InitialStateEditor } from "../panels/PropertiesPanel/initial-state-editor";
 import { usePlacePropertiesContext } from "../panels/PropertiesPanel/place-properties-context";
 
-const inputStyle = cva({
-  base: {
-    fontSize: "[14px]",
-    padding: "[6px 8px]",
-    borderRadius: "[4px]",
-    width: "[100%]",
-    boxSizing: "border-box",
-    border: "[1px solid rgba(0, 0, 0, 0.1)]",
-  },
-  variants: {
-    isDisabled: {
-      true: {
-        backgroundColor: "[rgba(0, 0, 0, 0.05)]",
-        cursor: "not-allowed",
-      },
-      false: {
-        backgroundColor: "[white]",
-        cursor: "text",
-      },
-    },
-  },
-  defaultVariants: {
-    isDisabled: false,
-  },
-});
-
 const fieldLabelStyle = css({
   fontWeight: "medium",
   fontSize: "[12px]",
@@ -127,10 +103,9 @@ const PlaceInitialStateContent: React.FC = () => {
     return (
       <div className={simpleStateContainerStyle}>
         <div className={fieldLabelStyle}>Token count</div>
-        <input
-          type="number"
-          min="0"
-          step="1"
+        <NumberInput
+          min={0}
+          step={1}
           value={currentTokenCount}
           onChange={(event) => {
             const count = Math.max(
@@ -143,7 +118,7 @@ const PlaceInitialStateContent: React.FC = () => {
             });
           }}
           disabled={hasSimulationFrames}
-          className={inputStyle({ isDisabled: hasSimulationFrames })}
+          tooltip={hasSimulationFrames ? UI_MESSAGES.READ_ONLY_MODE : undefined}
         />
       </div>
     );
diff --git a/libs/@hashintel/petrinaut/src/views/Editor/subviews/types-list.tsx b/libs/@hashintel/petrinaut/src/views/Editor/subviews/types-list.tsx
index 6adf4dd58b4..a53d0046f1e 100644
--- a/libs/@hashintel/petrinaut/src/views/Editor/subviews/types-list.tsx
+++ b/libs/@hashintel/petrinaut/src/views/Editor/subviews/types-list.tsx
@@ -2,9 +2,11 @@ import { css, cva } from "@hashintel/ds-helpers/css";
 import { use } from "react";
 
 import type { SubView } from "../../../components/sub-view/types";
-import { SimulationContext } from "../../../simulation/context";
+import { Tooltip } from "../../../components/tooltip";
+import { UI_MESSAGES } from "../../../constants/ui-messages";
 import { EditorContext } from "../../../state/editor-context";
 import { SDCPNContext } from "../../../state/sdcpn-context";
+import { useIsReadOnly } from "../../../state/use-is-read-only";
 
 const listContainerStyle = css({
   display: "flex",
@@ -171,10 +173,7 @@ const TypesSectionContent: React.FC = () => {
 
   const { selectedResourceId, setSelectedResourceId } = use(EditorContext);
 
-  // Check if simulation is running or paused
-  const { state: simulationState } = use(SimulationContext);
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
+  const isReadOnly = useIsReadOnly();
 
   return (
     <div className={listContainerStyle}>
@@ -208,24 +207,29 @@ const TypesSectionContent: React.FC = () => {
               style={{ backgroundColor: type.displayColor }}
             />
             <span className={typeNameStyle}>{type.name}</span>
-            <button
-              type="button"
-              disabled={isSimulationActive}
-              onClick={() => {
-                if (
-                  // eslint-disable-next-line no-alert
-                  window.confirm(
-                    `Delete token type "${type.name}"? All places using this type will have their type set to null.`,
-                  )
-                ) {
-                  removeType(type.id);
-                }
-              }}
-              className={deleteButtonStyle}
-              aria-label={`Delete token type ${type.name}`}
+            <Tooltip
+              content={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+              display="inline"
             >
-              ×
-            </button>
+              <button
+                type="button"
+                disabled={isReadOnly}
+                onClick={() => {
+                  if (
+                    // eslint-disable-next-line no-alert
+                    window.confirm(
+                      `Delete token type "${type.name}"? All places using this type will have their type set to null.`,
+                    )
+                  ) {
+                    removeType(type.id);
+                  }
+                }}
+                className={deleteButtonStyle}
+                aria-label={`Delete token type ${type.name}`}
+              >
+                ×
+              </button>
+            </Tooltip>
           </div>
         );
       })}
@@ -245,41 +249,43 @@ const TypesSectionHeaderAction: React.FC = () => {
     addType,
   } = use(SDCPNContext);
 
-  // Check if simulation is running or paused
-  const { state: simulationState } = use(SimulationContext);
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
+  const isReadOnly = useIsReadOnly();
 
   return (
-    <button
-      type="button"
-      disabled={isSimulationActive}
-      onClick={() => {
-        const existingColors = types.map((type) => type.displayColor);
-        const existingNames = types.map((type) => type.name);
-        const nextNumber = getNextTypeNumber(existingNames);
-        const nextColor = getNextAvailableColor(existingColors);
-
-        const newType = {
-          id: `type__${Date.now()}`,
-          name: `Type ${nextNumber}`,
-          iconSlug: "circle",
-          displayColor: nextColor,
-          elements: [
-            {
-              elementId: `element__${Date.now()}`,
-              name: "dimension_1",
-              type: "real" as const,
-            },
-          ],
-        };
-        addType(newType);
-      }}
-      className={addButtonStyle}
-      aria-label="Add token type"
+    <Tooltip
+      content={isReadOnly ? UI_MESSAGES.READ_ONLY_MODE : undefined}
+      display="inline"
     >
-      +
-    </button>
+      <button
+        type="button"
+        disabled={isReadOnly}
+        onClick={() => {
+          const existingColors = types.map((type) => type.displayColor);
+          const existingNames = types.map((type) => type.name);
+          const nextNumber = getNextTypeNumber(existingNames);
+          const nextColor = getNextAvailableColor(existingColors);
+
+          const newType = {
+            id: `type__${Date.now()}`,
+            name: `Type ${nextNumber}`,
+            iconSlug: "circle",
+            displayColor: nextColor,
+            elements: [
+              {
+                elementId: `element__${Date.now()}`,
+                name: "dimension_1",
+                type: "real" as const,
+              },
+            ],
+          };
+          addType(newType);
+        }}
+        className={addButtonStyle}
+        aria-label="Add token type"
+      >
+        +
+      </button>
+    </Tooltip>
   );
 };
 
diff --git a/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx b/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx
index 1ab40da6c44..f0f7fb4fdb8 100644
--- a/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx
+++ b/libs/@hashintel/petrinaut/src/views/SDCPN/sdcpn-view.tsx
@@ -10,9 +10,9 @@ import {
   DEFAULT_TRANSITION_KERNEL_CODE,
   generateDefaultLambdaCode,
 } from "../../core/default-codes";
-import { SimulationContext } from "../../simulation/context";
 import { EditorContext } from "../../state/editor-context";
 import { SDCPNContext } from "../../state/sdcpn-context";
+import { useIsReadOnly } from "../../state/use-is-read-only";
 import { Arc } from "./components/arc";
 import { PlaceNode } from "./components/place-node";
 import { TransitionNode } from "./components/transition-node";
@@ -65,7 +65,6 @@ export const SDCPNView: React.FC = () => {
   } = use(SDCPNContext);
 
   const {
-    globalMode: mode,
     editionMode,
     setEditionMode,
     selectedItemIds,
@@ -74,8 +73,6 @@ export const SDCPNView: React.FC = () => {
     clearSelection,
   } = use(EditorContext);
 
-  const { state: simulationState } = use(SimulationContext);
-
   // Hook for applying node changes
   const applyNodeChanges = useApplyNodeChanges();
 
@@ -87,10 +84,9 @@ export const SDCPNView: React.FC = () => {
     reactFlowInstance?.fitView({ padding: 0.4, minZoom: 0.4, maxZoom: 1.1 });
   }, [reactFlowInstance, petriNetId]);
 
-  // Readonly if in simulate mode, simulation is running/paused, or readonly has been provided by external consumer.
-  const isSimulationActive =
-    simulationState === "Running" || simulationState === "Paused";
-  const isReadonly = mode === "simulate" || isSimulationActive || readonly;
+  // Readonly if simulation mode or readonly has been provided by external consumer.
+  const isSimulationReadOnly = useIsReadOnly();
+  const isReadonly = isSimulationReadOnly || readonly;
 
   function isValidConnection(connection: Connection) {
     const sourceNode = nodes.find((node) => node.id === connection.source);
@@ -307,7 +303,7 @@ export const SDCPNView: React.FC = () => {
       onKeyDown={({ key }) => {
         // Quick-and-dirty way to delete selected items with keyboard
         // with two different keys (Delete and Backspace), not possible with ReactFlow `deleteKeyCode` prop
-        if (key === "Delete" || key === "Backspace") {
+        if ((key === "Delete" || key === "Backspace") && !isReadonly) {
           setSelectedResourceId(null);
           clearSelection();
           deleteItemsByIds(selectedItemIds);
