| | |

React Hook Form 24 ๐Ÿ“‹ DevTools โ€” Debugging Form State in the Browser

โฑ๏ธ Estimated read: 12 min | |

React Hook Form’s uncontrolled input model makes debugging different from controlled forms. The inputs hold their own values in the DOM, and React Hook Form tracks state through refs and subscriptions. A console.log of the form state object often prints stale data because the library updates state outside the render cycle. The official DevTools component solves this by subscribing to the form’s internal state and rendering it in a floating panel that updates as the user types, blurs, and submits.

This chapter covers the official @hookform/devtools package: installation, integration with useForm, the panel’s capabilities, and the debugging patterns that replace scattered console.log calls. It also addresses the Chrome extension variant, the limitations of the panel with uncontrolled inputs, and when to reach for the subscribe API instead. The sections that follow explain why form debugging is uniquely difficult, how the DevTools panel connects to the form’s control object, and how to read the state it displays.

Key point: The DevTools panel is a React component that receives the control object from useForm and renders live form state; it is a development-only tool that must not ship to production.


Why form debugging tools exist

The uncontrolled state problem. React Hook Form’s inputs are uncontrolled by design, which is the source of its performance advantage. The consequence is that React DevTools shows the input’s DOM value but not the form’s tracked stateโ€”dirtyFields, touchedFields, errors, and isValid live inside the library’s internal store, not in React state. A developer inspecting a component in React DevTools sees the props and the useForm return object, but that object’s properties do not update in the DevTools panel as the user types .

The stale snapshot problem. Calling getValues() in a render or event handler returns the current value, but calling it in a useEffect or logging formState directly can capture a snapshot that does not reflect the latest interaction. The DevTools panel solves this by re-reading the form state on demand and on a refresh trigger, because React Hook Form’s uncontrolled inputs do not automatically push updates to the panel .

The production exclusion problem. DevTools adds weight and renders a floating UI that must never appear for end users. The package is installed as a dev dependency and the component is typically rendered conditionally or tree-shaken in production builds. A form that imports DevTool unconditionally ships debugging UI to every user .


a. Installing and integrating the DevTools panel

The package is installed as a development dependency and imported only in development code paths.

# Install as a dev dependency, never a runtime dependency
npm install -D @hookform/devtools

The DevTool component requires the control object from useForm. Without it, the panel has no form to inspect and renders nothing. The component is placed anywhere in the component tree, typically after the form element so it does not interfere with layout.

import { useForm } from "react-hook-form";
import { DevTool } from "@hookform/devtools";

export default function SignupForm() {
  const { register, control, handleSubmit } = useForm({
    mode: "onChange", // validation on change makes the panel update as you type
  });

  return (
    <>
      <form onSubmit={handleSubmit((data) => console.log(data))}>
        <label htmlFor="email">Email</label>
        <input id="email" {...register("email", { required: "Required" })} />
        <button type="submit">Submit</button>
      </form>

      {/* The panel renders bottom-right; pass control, not the whole form object */}
      <DevTool control={control} />
    </>
  );
}

The mode: "onChange" option is not required, but it makes the DevTools panel more responsive. With the default onSubmit mode, validation state changes only after submission, so the panel’s error display remains empty until the user submits .


b. Reading the panel’s state output

The panel displays several categories of state that correspond to formState properties. Understanding what each shows prevents misreading the panel.

// The panel reads from the same internal store as formState
const {
  register,
  control,
  formState: {
    errors,        // Shown in panel: validation failures per field
    dirtyFields,   // Shown: fields whose values differ from defaultValues
    touchedFields, // Shown: fields that have received and lost focus
    isValid,       // Shown: true when no errors and constraints pass
    isSubmitting,  // Shown: true during async onSubmit
  },
} = useForm();

The values section shows the current form values read from the uncontrolled inputs. Because the inputs are uncontrolled, the panel provides an Update button that forces a re-read of the latest input values; the panel does not automatically reflect every keystroke unless validation mode triggers a state update . This is the most common source of confusion: typing a value and seeing the panel unchanged does not mean the value is missing from the formโ€”it means the panel has not re-read the DOM.

The errors section shows messages keyed by field name. Clicking a field in the panel’s search can locate the corresponding input in the form, which is useful for large forms with nested field arrays . The valid/invalid indicator mirrors formState.isValid, which depends on the validation mode: in onSubmit mode it stays true until the first submission fails.


c. The Chrome extension variant

A Chrome extension exists that moves the panel into the browser’s DevTools rather than a floating overlay. It requires the same @hookform/devtools package and the same <DevTool control={control} /> component, but renders nothing in the page; the extension detects the component and displays the state inside Chrome DevTools .

// With the extension installed, this component renders nothing in the page
// The extension reads it and displays state in Chrome DevTools
import { DevTool } from "@hookform/devtools";

<DevTool id="signup-form" control={control} />

The id prop is optional; without it, the extension generates a UUID. The benefit of the extension is that it does not cover page content and integrates with the browser’s existing DevTools tabs. The requirement is React Hook Form >= 7.33.1 and @hookform/devtools >= 4.2.2 . The limitation is that it is Chrome-only and requires a separate installation step for every developer on the team.


Complete Example Session

// ============================================
// PART 1: THE FORM WITH DEVTOOLS INTEGRATED
// ============================================
import { useForm } from "react-hook-form";
import { DevTool } from "@hookform/devtools";

type FormValues = {
  username: string;
  email: string;
  age: number;
};

export default function RegistrationForm() {
  const {
    register,
    control,
    handleSubmit,
    formState: { errors, dirtyFields, isSubmitting },
  } = useForm<FormValues>({
    mode: "onChange", // makes the DevTools panel update as the user types
    defaultValues: {
      username: "",
      email: "",
      age: 18,
    },
  });

  const onSubmit = async (data: FormValues) => {
    await new Promise((r) => setTimeout(r, 500));
    console.log("Submitted:", data);
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)} noValidate>
        <div>
          <label htmlFor="username">Username</label>
          <input
            id="username"
            aria-invalid={!!errors.username}
            {...register("username", {
              required: "Username is required.",
              minLength: { value: 3, message: "At least 3 characters." },
            })}
          />
          {errors.username && <p role="alert">{errors.username.message}</p>}
        </div>

        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            type="email"
            aria-invalid={!!errors.email}
            {...register("email", {
              required: "Email is required.",
              pattern: {
                value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
                message: "Enter a valid email.",
              },
            })}
          />
          {errors.email && <p role="alert">{errors.email.message}</p>}
        </div>

        <div>
          <label htmlFor="age">Age</label>
          <input
            id="age"
            type="number"
            aria-invalid={!!errors.age}
            {...register("age", {
              valueAsNumber: true,
              min: { value: 18, message: "Must be 18 or older." },
            })}
          />
          {errors.age && <p role="alert">{errors.age.message}</p>}
        </div>

        <button type="submit" disabled={isSubmitting}>
          {isSubmitting ? "Submittingโ€ฆ" : "Register"}
        </button>
      </form>

      {/* DevTools panel: bottom-right floating UI */}
      <DevTool control={control} />
    </>
  );
}

// ============================================
// PART 2: WHAT THE PANEL SHOWS AT EACH STAGE
// ============================================
// On initial render:
//   values: { username: "", email: "", age: 18 }
//   errors: {}
//   dirtyFields: {}
//   isValid: true (no validation has run in onChange mode until first change)
//
// After typing "ab" in username:
//   values: { username: "ab", email: "", age: 18 } (after Update click)
//   errors: { username: { message: "At least 3 characters." } }
//   dirtyFields: { username: true }
//
// After submitting with valid values:
//   isSubmitting: true, then false
//   values reflect the submitted payload in the console

// ============================================
// PART 3: CONDITIONAL RENDERING FOR PRODUCTION
// ============================================
import { lazy, Suspense } from "react";

// Lazy-load DevTools only in development
const DevTools =
  import.meta.env.DEV
    ? lazy(() =>
        import("@hookform/devtools").then((mod) => ({
          default: mod.DevTool,
        }))
      )
    : () => null;

// In the component:
// <Suspense fallback={null}>
//   <DevTools control={control} />
// </Suspense>

The parts covered the form with DevTools wired to the control object, the state transitions the panel displays at each interaction stage, and the production exclusion pattern using lazy loading and import.meta.env.DEV.


Quick Reference

DevTools Setup

ItemValue
Package@hookform/devtools
Install commandnpm install -D @hookform/devtools
Importimport { DevTool } from "@hookform/devtools"
Required propcontrol from useForm
Optional propid (for Chrome extension)
Render locationAnywhere in the component tree

Panel Sections

SectionShowsSource
ValuesCurrent input valuesDOM inputs (needs Update)
ErrorsValidation messagesformState.errors
Dirty FieldsChanged fieldsformState.dirtyFields
TouchedBlurred fieldsformState.touchedFields
ValidOverall validityformState.isValid
SearchLocate inputClick “Native” button

Validation Modes and Panel Responsiveness

ModePanel Updates
onSubmitOnly after submit
onChangeAs user types
onBlurWhen field loses focus
onTouchedFirst blur, then change

Production Exclusion

MethodHow
Lazy + DEV flagimport.meta.env.DEV ? lazy(...) : () => null
Conditional render{process.env.NODE_ENV === "development" && <DevTool />}
Build exclusionTree-shaking with side-effect-free import

Best Practices

โœ… Do This:

// Install as dev dependency only
npm install -D @hookform/devtools                              // โœ…

// Pass the control object, not the form object
<DevTool control={control} />                                  // โœ…

// Use onChange mode for responsive debugging
useForm({ mode: "onChange" })                                  // โœ…

// Conditionally render in production
{import.meta.env.DEV && <DevTool control={control} />}         // โœ…

// Click Update in the panel after typing
// (uncontrolled inputs do not push automatically)              // โœ…

// Use the search/Native button for large forms
// to locate inputs in the panel                                // โœ…

โŒ Don’t Do This:

// Install as production dependency
npm install @hookform/devtools                                 // โŒ

// Forget the control prop
<DevTool />                                                    // โŒ (renders nothing)

// Ship DevTools unconditionally
<DevTool control={control} /> // in production                 // โŒ

// Expect automatic value updates in onSubmit mode
// (panel shows stale values until Update or submit)           // โŒ

// Log formState in useEffect and expect fresh data
useEffect(() => console.log(formState), [formState]);          // โŒ (stale)

Common Pitfalls

PitfallWhy It HappensFix
Panel renders nothingcontrol prop missingPass control from useForm
Values do not update while typingUncontrolled inputs + onSubmit modeSwitch to onChange or click Update
DevTools visible in productionImport not guardedConditional render or lazy load
Panel covers form elementsDefault position bottom-rightUse placement prop or Chrome extension
Cannot find input in large formManual search by nameClick “Native” button in panel
Extension does not detect formVersion mismatchRHF >= 7.33.1, devtools >= 4.2.2
formState logged outside DevTools is staleSnapshot captured before updateUse subscribe API or DevTools panel

Real-World Examples

1. Minimal DevTools integration

const { control } = useForm();
return <DevTool control={control} />;

2. Responsive panel with onChange mode

useForm({ mode: "onChange" });

3. Custom placement

<DevTool control={control} placement="top-left" />

4. Chrome extension with explicit id

<DevTool id="registration-form" control={control} />

5. Production exclusion with NODE_ENV

{process.env.NODE_ENV === "development" && <DevTool control={control} />}

6. Lazy loading for bundle exclusion

const DevTools = lazy(() => import("@hookform/devtools").then(m => ({ default: m.DevTool })));

7. Using subscribe for console debugging without re-renders

useEffect(() => subscribe({
  formState: { values: true },
  callback: ({ values }) => console.log(values),
}), [subscribe]);

8. Checking dirty state in the panel

// Panel shows dirtyFields after typing; compare with defaultValues

9. Finding a nested field array input

// Type field name in panel search, click Native to scroll to input

10. Verifying submission payload

// After submit, check console.log(data) alongside the panel's values

Visual

The DevTools Data Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  HOW THE PANEL READS STATE                              โ”‚
โ”‚                                                         โ”‚
โ”‚  <form>                                                 โ”‚
โ”‚    <input {...register("email")} />                     โ”‚
โ”‚         โ”‚                                               โ”‚
โ”‚         โ–ผ                                               โ”‚
โ”‚  React Hook Form internal store                         โ”‚
โ”‚  (errors, dirtyFields, touchedFields, isValid)          โ”‚
โ”‚         โ”‚                                               โ”‚
โ”‚         โ”‚  control prop connects the store              โ”‚
โ”‚         โ–ผ                                               โ”‚
โ”‚  <DevTool control={control} />                          โ”‚
โ”‚         โ”‚                                               โ”‚
โ”‚         โ–ผ                                               โ”‚
โ”‚  Floating panel renders:                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                  โ”‚
โ”‚  โ”‚ Values   โ”‚ Errors   โ”‚ Dirty       โ”‚                  โ”‚
โ”‚  โ”‚ { ... }  โ”‚ { ... }  โ”‚ { ... }     โ”‚                  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ”‚
โ”‚                                                         โ”‚
โ”‚  Uncontrolled inputs do not push to the panel;          โ”‚
โ”‚  Update button re-reads DOM values.                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Panel State at Each Interaction

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  STATE TRANSITIONS VISIBLE IN PANEL                     โ”‚
โ”‚                                                         โ”‚
โ”‚  Initial:                                               โ”‚
โ”‚  values: { username: "" }                               โ”‚
โ”‚  errors: {}                                             โ”‚
โ”‚  dirtyFields: {}                                        โ”‚
โ”‚  isValid: true                                          โ”‚
โ”‚         โ”‚                                               โ”‚
โ”‚         โ–ผ  user types "ab" in username                  โ”‚
โ”‚  values: { username: "ab" }  (after Update)             โ”‚
โ”‚  errors: { username: "At least 3 characters." }         โ”‚
โ”‚  dirtyFields: { username: true }                        โ”‚
โ”‚  isValid: false                                         โ”‚
โ”‚         โ”‚                                               โ”‚
โ”‚         โ–ผ  user types "abc"                             โ”‚
โ”‚  errors: {}                                             โ”‚
โ”‚  isValid: true                                          โ”‚
โ”‚         โ”‚                                               โ”‚
โ”‚         โ–ผ  user clicks Submit                           โ”‚
โ”‚  isSubmitting: true โ†’ false                             โ”‚
โ”‚  onSubmit receives { username: "abc", ... }             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Floating Panel vs Chrome Extension

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  TWO DEVTOOLS VARIANTS                                  โ”‚
โ”‚                                                         โ”‚
โ”‚  Floating panel:                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”‚
โ”‚  โ”‚  App page                                โ”‚           โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                  โ”‚           โ”‚
โ”‚  โ”‚  โ”‚ Form               โ”‚                  โ”‚           โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ”‚           โ”‚
โ”‚  โ”‚                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚           โ”‚
โ”‚  โ”‚                    โ”‚ DevTool     โ”‚       โ”‚           โ”‚
โ”‚  โ”‚                    โ”‚ panel       โ”‚       โ”‚           โ”‚
โ”‚  โ”‚                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ”‚  In-page, covers content, works in any browser          โ”‚
โ”‚                                                         โ”‚
โ”‚  Chrome extension:                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”           โ”‚
โ”‚  โ”‚  App page (no overlay)                   โ”‚           โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                  โ”‚           โ”‚
โ”‚  โ”‚  โ”‚ Form               โ”‚                  โ”‚           โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                  โ”‚           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           โ”‚
โ”‚  Chrome DevTools tab shows form state                   โ”‚
โ”‚  No page overlay, Chrome only                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Package@hookform/devtools
Installnpm install -D @hookform/devtools
Component<DevTool control={control} />
Required propcontrol from useForm
Panel locationBottom-right by default
Value updatesManual Update click (uncontrolled inputs)
Chrome extensionRHF >= 7.33.1, devtools >= 4.2.2
ProductionConditional render or lazy load
Alternative for logssubscribe API (no re-renders)
Panel search“Native” button locates inputs

Key takeaways:

  • DevTools is a component, not a browser extension by default. It renders a floating panel in your app and requires the control object from useForm to connect to the form’s internal store .
  • Uncontrolled inputs mean manual refresh. The panel does not automatically reflect every keystroke; the Update button re-reads the DOM values, and onChange validation mode makes updates more frequent .
  • The panel shows what formState hides. Errors, dirty fields, touched fields, and validity are visible without scattering console.log calls or inspecting React DevTools.
  • The Chrome extension variant moves the panel into DevTools. It requires the same package and component but renders nothing in the page, at the cost of Chrome-only support .
  • Never ship DevTools to production. Guard with import.meta.env.DEV, process.env.NODE_ENV, or lazy loading so the panel and its dependencies are excluded from production bundles.
  • For programmatic logging, use subscribe. The subscribe API provides form state changes outside the render cycle without the panel, useful for autosave drafts or analytics .

Remember: React Hook Form DevTools solves a specific problem: seeing the library’s internal state while the inputs remain uncontrolled. It is not a replacement for React DevTools or browser DevToolsโ€”it is the only window into formState that updates as the form is used. Install it as a dev dependency, pass control, guard the render for production, and use onChange mode to make the panel responsive. When the form misbehaves in a way that console.log cannot capture, the DevTools panel shows the state that explains why.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!