Vue.js 39 🟢 Type-Safe Injection Keys using InjectionKey Symbols
The provide() and the inject() pair makes the value available deep in the tree, but the key is a string by default, and the string is not the type-safe. The inject('theme') returns the unknown, and the TypeScript cannot check the type. The developer must cast the value, and the cast is the runtime risk. The InjectionKey<T> is the Vue’s solution: the typed symbol that carries the value type in the TypeScript. The InjectionKey<ThemeContext> declares the key and the value type, and the provide(key, value) and the inject(key) are the type-checked. The compiler catches the wrong type, and the value is the typed without the cast. The typed key is the foundation of the type-safe dependency injection in the Vue 3 and the TypeScript.
The InjectionKey is the interface that extends the Symbol. The const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme') creates the key with the type parameter. The provide(ThemeKey, themeContext) requires the themeContext to match the ThemeContext type, and the inject(ThemeKey) returns the ThemeContext | undefined. The type parameter is the contract: the provider and the injector must agree on the shape, and the compiler enforces the agreement.
The typed key is especially valuable in the shared libraries. The library exports the InjectionKey and the type, and the consumer imports the key and the type. The consumer’s provide and inject are the type-checked against the library’s contract, and the mismatch is the compile error. The useX() composable wraps the inject() and throws when the key is missing, and the return type is the typed. The library’s API is the typed, and the consumer’s usage is the safe.
This chapter covers three areas. First, why the typed injection keys exist — the string-key problem and the type-safety. Second, the InjectionKey syntax — the declaration, the type parameter, the provide and the inject with the typed key, and the default value. Third, the patterns and the pitfalls — the library API, the composable wrapper, the multiple keys, the type inference, and the common mistakes. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the typed injection flow.
Key point: The InjectionKey<T> is the typed symbol that carries the value type. The const Key: InjectionKey<T> = Symbol('key') declares the key and the type. The provide(Key, value) requires the value to match the type, and the inject(Key) returns the typed value or the undefined. The InjectionKey makes the provide and the inject the type-safe, and the shared libraries use it to export the typed API.
Why the typed injection keys exist
The string-key problem. The inject('theme') with the string key returns the unknown. The TypeScript cannot check the type, and the developer must cast the value. The cast is the runtime risk: the wrong cast is the runtime error, and the compiler does not catch it. The string key is the convenient and the unsafe.
The type-safety problem. The provide and the inject are the separate files. The provider defines the value, and the injector reads it. The two must agree on the type, and the string key does not enforce the agreement. The InjectionKey<T> carries the type, and the compiler checks the provider and the injector against the same T. The mismatch is the compile error.
The contract problem. The library exports the key and the type, and the consumer imports them. The key is the token, and the type is the shape. The consumer’s provide and inject are the type-checked against the library’s contract, and the wrong value is the compile error. The contract is the explicit, and the InjectionKey is the carrier.
The refactor problem. The string keys are the typo-prone. The inject('theme') and the inject('themes') are the different keys, and the typo is the silent undefined. The InjectionKey is the variable, and the typo is the compile error. The rename is the safe, because the IDE renames the variable and the imports.
The Symbol problem. The string keys are the global, and the two libraries can use the same string. The Symbol is the unique, and the InjectionKey extends the Symbol. The collision is the impossible, and the typed key is the unique and the typed.
The trade-off. The InjectionKey adds the boilerplate: the declaration, the type, and the import. The string key is the shorter, and the typed key is the safer. The trade-off is between the brevity of the string and the type-safety of the InjectionKey, and the TypeScript projects choose the InjectionKey.
a. The InjectionKey declaration and usage
The InjectionKey is imported from the vue and declared with the type parameter:
import type { InjectionKey } from 'vue';
interface ThemeContext {
theme: string;
toggleTheme: () => void;
}
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
The InjectionKey<ThemeContext> declares the key with the type, and the Symbol('theme') creates the runtime symbol. The ThemeKey is the typed key, and the ThemeContext is the value type.
The provider uses the key:
import { provide, ref, readonly } from 'vue';
import { ThemeKey } from './keys';
const theme = ref('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
provide(ThemeKey, {
theme: theme.value,
toggleTheme,
});
The provide(ThemeKey, value) requires the value to match the ThemeContext type. The wrong value is the compile error.
The injector uses the key:
import { inject } from 'vue';
import { ThemeKey } from './keys';
const themeContext = inject(ThemeKey);
// The type is ThemeContext | undefined.
if (!themeContext) {
throw new Error('The ThemeKey is missing');
}
// The type is ThemeContext.
themeContext.toggleTheme();
The inject(ThemeKey) returns the ThemeContext | undefined. The undefined is the possible when no provider exists, and the check narrows the type.
The default value. The inject() with the default returns the non-undefined type:
const themeContext = inject(ThemeKey, {
theme: 'light',
toggleTheme: () => {},
});
// The type is ThemeContext (the not undefined).
The default is the ThemeContext, and the return type is the non-undefined. The default eliminates the undefined check, but it hides the missing provider.
The factory default. The inject() with the factory:
const themeContext = inject(ThemeKey, () => ({
theme: 'light',
toggleTheme: () => {},
}));
The factory is the lazy, and the return type is the non-undefined.
The readonly. The readonly wraps the state, and the type is the Readonly<Ref<T>>:
import { provide, ref, readonly } from 'vue';
const theme = ref('light');
provide(ThemeKey, {
theme: readonly(theme),
toggleTheme,
});
The theme in the context is the Readonly<Ref<string>>, and the descendants cannot mutate it.
b. The library API and the composable
The library’s keys file. The library exports the keys and the types:
// the-library/keys.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Readonly<Ref<string>>;
toggleTheme: () => void;
}
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
The ThemeKey is the typed, and the ThemeContext is the exported type. The consumer imports both.
The library’s composable. The library provides the useX() composable that wraps the inject() and throws when the key is missing:
// the-library/composables.ts
import { inject } from 'vue';
import { ThemeKey, type ThemeContext } from './keys';
export function useTheme(): ThemeContext {
const context = inject(ThemeKey);
if (!context) {
throw new Error('The useTheme must be used within the ThemeProvider');
}
return context;
}
The useTheme() returns the ThemeContext, and the undefined is the handled. The consumer does not check the undefined.
The consumer’s provider. The consumer’s provider uses the key:
// the-app/ThemeProvider.vue
<script setup lang="ts">
import { provide, ref, readonly } from 'vue';
import { ThemeKey } from 'the-library';
const theme = ref('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
provide(ThemeKey, {
theme: readonly(theme),
toggleTheme,
});
</script>
<template>
<slot />
</template>
The provide(ThemeKey, value) is the type-checked, and the theme is the Readonly<Ref<string>>.
The consumer’s injector. The consumer’s component uses the useTheme():
<!-- the-app/ThemeToggle.vue -->
<script setup lang="ts">
import { useTheme } from 'the-library';
const { theme, toggleTheme } = useTheme();
</script>
<template>
<button @click="toggleTheme">
The theme is {{ theme }}
</button>
</template>
The useTheme() is the typed, and the theme is the Readonly<Ref<string>>. The template unwraps the ref.
The app.provide(). The application-level provide with the typed key:
import { createApp } from 'vue';
import { ThemeKey } from 'the-library';
const app = createApp(App);
app.provide(ThemeKey, {
theme: readonly(ref('light')),
toggleTheme: () => {},
});
The app.provide(ThemeKey, value) is the type-checked, and the value must match the ThemeContext.
c. The patterns and the pitfalls
The multiple keys. The library can export the multiple typed keys:
// the-library/keys.ts
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
export const AuthKey: InjectionKey<AuthContext> = Symbol('auth');
export const I18nKey: InjectionKey<I18nContext> = Symbol('i18n');
The consumer imports the specific key, and the types are the separate.
The type inference. The InjectionKey<T> carries the type, and the inject(Key) infers the T | undefined:
const themeContext = inject(ThemeKey);
// The type: ThemeContext | undefined
The TypeScript infers the type from the key, and the cast is the unnecessary.
The InjectionKey with the ComputedRef. The provided value can be the computed:
interface CounterContext {
count: ComputedRef<number>;
increment: () => void;
}
const CounterKey: InjectionKey<CounterContext> = Symbol('counter');
provide(CounterKey, {
count: computed(() => count.value * 2),
increment,
});
The ComputedRef is the readonly by the nature, and the consumer reads the .value.
The InjectionKey with the Ref. The provided value can be the ref:
interface CounterContext {
count: Ref<number>;
increment: () => void;
}
const CounterKey: InjectionKey<CounterContext> = Symbol('counter');
provide(CounterKey, {
count,
increment,
});
The Ref is the mutable, and the consumer can mutate it. The readonly(count) is the safer.
The wrong type. The provide(ThemeKey, wrongValue) is the compile error:
// ❌ The wrong type
provide(ThemeKey, {
theme: 'light', // the should be the Ref<string>
toggleTheme,
});
The compiler checks the value against the ThemeContext, and the wrong shape is the error.
The missing check. The inject(ThemeKey) returns the undefined when the provider is missing:
// ❌ The no check
const themeContext = inject(ThemeKey);
themeContext.toggleTheme(); // the runtime error if undefined
// ✅ The check
const themeContext = inject(ThemeKey);
if (!themeContext) throw new Error('The ThemeKey is missing');
themeContext.toggleTheme();
The undefined check is the requirement, or the useX() composable handles it.
The Symbol description. The Symbol('theme') description is the debugging help, and the two Symbol('theme') are the different symbols:
const Key1 = Symbol('theme');
const Key2 = Symbol('theme');
console.log(Key1 === Key2); // false
The Symbol is the unique, and the description is the debugging.
The InjectionKey in the JavaScript. The InjectionKey is the TypeScript-only, and the JavaScript uses the string or the Symbol:
// The JavaScript
const ThemeKey = Symbol('theme');
provide(ThemeKey, theme);
const theme = inject(ThemeKey);
The InjectionKey is the type annotation, and the runtime is the Symbol.
Complete Example Session
// ============================================
// PART 1: THE TYPED KEY DECLARATION
// ============================================
import type { InjectionKey, Ref } from 'vue';
interface ThemeContext {
theme: Readonly<Ref<string>>;
toggleTheme: () => void;
}
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
// ============================================
// PART 2: THE PROVIDER
// ============================================
import { provide, ref, readonly } from 'vue';
import { ThemeKey } from './keys';
const theme = ref('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
provide(ThemeKey, {
theme: readonly(theme),
toggleTheme,
});
// ============================================
// PART 3: THE INJECTOR WITH THE CHECK
// ============================================
import { inject } from 'vue';
import { ThemeKey } from './keys';
const themeContext = inject(ThemeKey);
if (!themeContext) {
throw new Error('The ThemeKey is missing');
}
themeContext.toggleTheme();
// ============================================
// PART 4: THE COMPOSABLE WRAPPER
// ============================================
import { inject } from 'vue';
import { ThemeKey, type ThemeContext } from './keys';
export function useTheme(): ThemeContext {
const context = inject(ThemeKey);
if (!context) {
throw new Error('The useTheme must be used within the ThemeProvider');
}
return context;
}
// ============================================
// PART 5: THE LIBRARY'S KEYS FILE
// ============================================
// the-library/keys.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Readonly<Ref<string>>;
toggleTheme: () => void;
}
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
// ============================================
// PART 6: THE CONSUMER'S PROVIDER
// ============================================
<script setup lang="ts">
import { provide, ref, readonly } from 'vue';
import { ThemeKey } from 'the-library';
const theme = ref('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
provide(ThemeKey, {
theme: readonly(theme),
toggleTheme,
});
</script>
<template>
<slot />
</template>
// ============================================
// PART 7: THE CONSUMER'S INJECTOR
// ============================================
<script setup lang="ts">
import { useTheme } from 'the-library';
const { theme, toggleTheme } = useTheme();
</script>
<template>
<button @click="toggleTheme">
The theme is {{ theme }}
</button>
</template>
// ============================================
// PART 8: THE MULTIPLE KEYS
// ============================================
// the-library/keys.ts
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
export const AuthKey: InjectionKey<AuthContext> = Symbol('auth');
export const I18nKey: InjectionKey<I18nContext> = Symbol('i18n');
// ============================================
// PART 9: THE APP.PROVIDE
// ============================================
import { createApp, ref, readonly } from 'vue';
import { ThemeKey } from 'the-library';
const app = createApp(App);
app.provide(ThemeKey, {
theme: readonly(ref('light')),
toggleTheme: () => {},
});
// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================
// the-library/keys.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Readonly<Ref<string>>;
toggleTheme: () => void;
}
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
// the-library/composables.ts
import { inject } from 'vue';
import { ThemeKey, type ThemeContext } from './keys';
export function useTheme(): ThemeContext {
const context = inject(ThemeKey);
if (!context) {
throw new Error('The useTheme must be used within the ThemeProvider');
}
return context;
}
// the-app/ThemeProvider.vue
<script setup lang="ts">
import { provide, ref, readonly } from 'vue';
import { ThemeKey } from 'the-library';
const theme = ref('light');
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light';
}
provide(ThemeKey, {
theme: readonly(theme),
toggleTheme,
});
</script>
<template>
<slot />
</template>
// the-app/ThemeToggle.vue
<script setup lang="ts">
import { useTheme } from 'the-library';
const { theme, toggleTheme } = useTheme();
</script>
<template>
<button @click="toggleTheme">
The theme is {{ theme }}
</button>
</template>
The ten parts show the typed key declaration, the provider, the injector with the check, the composable wrapper, the library’s keys file, the consumer’s provider, the consumer’s injector, the multiple keys, the app.provide(), and the complete example.
Quick Reference
The InjectionKey Declaration
| The part | The example |
|---|---|
| The import | import type { InjectionKey } from 'vue' |
| The declaration | const Key: InjectionKey<T> = Symbol('key') |
| The type | The interface T { ... } |
| The runtime | The Symbol('key') |
The provide and the inject
| The function | The type |
|---|---|
provide(Key, value) | The value must match the T |
inject(Key) | The T | undefined |
inject(Key, default) | The T |
inject(Key, () => default) | The T |
The Type Inference
| The expression | The type |
|---|---|
inject(ThemeKey) | The ThemeContext | undefined |
inject(ThemeKey, default) | The ThemeContext |
provide(ThemeKey, value) | The value must be the ThemeContext |
The Library API
| The file | The content |
|---|---|
The keys.ts | The InjectionKey<T> and the interface T |
The composables.ts | The useX() that wraps the inject() |
The index.ts | The re-exports |
The Common Types
| The type | The use |
|---|---|
Ref<T> | The mutable |
Readonly<Ref<T>> | The readonly |
ComputedRef<T> | The computed |
() => void | The function |
Best Practices
✅ Do This:
// Use the InjectionKey for the type safety
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme'); // ✅
// Use the composable wrapper
export function useTheme(): ThemeContext {
const context = inject(ThemeKey);
if (!context) throw new Error('The useTheme requires the ThemeProvider');
return context;
} // ✅
// Use the readonly for the state
provide(ThemeKey, { theme: readonly(theme), toggleTheme }); // ✅
// Use the Symbol for the runtime
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme'); // ✅
// Use the interface for the context
export interface ThemeContext { theme: Readonly<Ref<string>>; } // ✅
// Export the key and the type from the library
export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme'); // ✅
❌ Don’t Do This:
// Don't use the string key in the TypeScript
const theme = inject('theme'); // the unknown // ❌
// Don't forget the undefined check
const theme = inject(ThemeKey);
theme.toggleTheme(); // the runtime error if undefined // ❌
// Don't use the wrong type
provide(ThemeKey, { theme: 'light' }); // the should be the Ref // ❌
// Don't cast the value
const theme = inject(ThemeKey) as ThemeContext; // the unsafe // ❌
// Don't use the same Symbol twice
const Key1 = Symbol('theme');
const Key2 = Symbol('theme'); // the different // ❌
// Don't forget the readonly
provide(ThemeKey, { theme, toggleTheme }); // the mutation risk // ❌
// Don't use the InjectionKey in the JavaScript runtime
// The InjectionKey is the TypeScript-only // ✅ (the Symbol is the runtime)
Common Pitfalls
| The pitfall | The why | The fix |
|---|---|---|
The unknown type | The string key | Use the InjectionKey |
| The runtime error | The no undefined check | Add the check or the useX() |
| The wrong type | The no type parameter | Add the InjectionKey<T> |
| The missing provider | The no useX() | Use the composable |
| The mutation | The no readonly | Wrap in the readonly |
| The key collision | The string | Use the Symbol |
| The type mismatch | The interface | Check the interface |
Real-World Examples
1. The Typed Key
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
2. The Provider
provide(ThemeKey, { theme: readonly(theme), toggleTheme });
3. The Injector
const context = inject(ThemeKey);
4. The Composable
export function useTheme(): ThemeContext { ... }
5. The Default
const context = inject(ThemeKey, defaultContext);
6. The Multiple Keys
export const AuthKey: InjectionKey<AuthContext> = Symbol('auth');
7. The app.provide
app.provide(ThemeKey, themeContext);
8. The Interface
export interface ThemeContext { theme: Readonly<Ref<string>>; }
9. The Library Export
export { ThemeKey, type ThemeContext } from './keys';
10. The Complete
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme');
Visual
The Typed Injection Flow
┌──────────────────────────────────────────────────────────────┐
│ THE TYPED INJECTION FLOW │
│ │
│ the-library/keys.ts: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ export interface ThemeContext { │ │
│ │ theme: Readonly<Ref<string>>; │ │
│ │ toggleTheme: () => void; │ │
│ │ } │ │
│ │ │ │
│ │ export const ThemeKey: │ │
│ │ InjectionKey<ThemeContext> = Symbol('theme'); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ├── the provider imports the key │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ provide(ThemeKey, themeContext); │ │ │
│ │ │ // the value must match the ThemeContext │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │
│ └── the injector imports the key │ │
│ ┌──────────────────────────────────────────────┐ │ │
│ │ const context = inject(ThemeKey); │ │ │
│ │ // the type: ThemeContext | undefined │ │ │
│ └──────────────────────────────────────────────┘ │ │
│ │
│ The compiler checks the provider and the injector. │
│ │
└──────────────────────────────────────────────────────────────┘
The Composable Wrapper
┌──────────────────────────────────────────────────────────────┐
│ THE COMPOSABLE WRAPPER │
│ │
│ the-library/composables.ts: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ export function useTheme(): ThemeContext { │ │
│ │ const context = inject(ThemeKey); │ │
│ │ if (!context) { │ │
│ │ throw new Error('The useTheme must be used │ │
│ │ within the ThemeProvider'); │ │
│ │ } │ │
│ │ return context; │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ │ the consumer │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ const { theme, toggleTheme } = useTheme(); │ │
│ │ │ │
│ │ The type is ThemeContext, the not undefined. │ │
│ │ The runtime check is the built-in. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
The Interface and the Key
┌──────────────────────────────────────────────────────────────┐
│ THE INTERFACE AND THE KEY │
│ │
│ The interface: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ export interface ThemeContext { │ │
│ │ theme: Readonly<Ref<string>>; │ │
│ │ toggleTheme: () => void; │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The key: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ export const ThemeKey: │ │
│ │ InjectionKey<ThemeContext> = Symbol('theme'); │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The key carries the type. The provide and the inject │
│ are the type-checked against the interface. │
│ │
└──────────────────────────────────────────────────────────────┘
The Type Inference
┌──────────────────────────────────────────────────────────────┐
│ THE TYPE INFERENCE │
│ │
│ const context = inject(ThemeKey); │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The type: ThemeContext | undefined │ │
│ │ │ │
│ │ if (!context) throw new Error(...); │ │
│ │ │ │
│ │ The type: ThemeContext │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ const context = inject(ThemeKey, defaultContext); │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ The type: ThemeContext (the not undefined) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The InjectionKey infers the type from the key. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
InjectionKey<T> | The typed symbol |
| The declaration | const Key: InjectionKey<T> = Symbol('key') |
The provide | provide(Key, value) |
The inject | inject(Key) returns the T | undefined |
| The default | inject(Key, default) returns the T |
| The composable | The useX() wrapper |
| The interface | The exported type |
| The library | The exported key and type |
The Symbol | The runtime |
| The type | The compile-time |
Key takeaways:
- The
InjectionKey<T>is the typed symbol that carries the value type. Theconst Key: InjectionKey<T> = Symbol('key')declares the key and the type, and theprovide(Key, value)and theinject(Key)are the type-checked. - The
inject(Key)returns theT | undefined. The undefined is the possible when no provider exists, and the check narrows the type. Theinject(Key, default)returns theTwithout the undefined. - The composable wrapper handles the undefined. The
useX()wraps theinject()and throws when the key is missing. The consumer uses theuseX()and the return type is the typed. - The library exports the key and the type. The consumer imports the
InjectionKeyand the interface, and theprovideand theinjectare the type-checked against the contract. - The
Symbolis the runtime, and the type is the compile-time. TheInjectionKeyextends theSymbol, and the type parameter is the TypeScript-only. The runtime is the unique symbol. - The
readonlyis the safe state. The provider wraps the state in thereadonlyand passes the update function. The consumer cannot mutate the state directly. - The type mismatch is the compile error. The
provide(Key, wrongValue)is the compile error, and theinject(Key)returns the typed value. The compiler catches the mismatch. - The result is the type-safe dependency injection. The provider and the injector agree on the type, and the compiler enforces the agreement. The result is the foundation of the type-safe library API.
Remember: The InjectionKey<T> makes the provide and the inject the type-safe. The key carries the type, and the compiler checks the provider and the injector. The useX() composable handles the undefined, and the library exports the key and the type. The result is the type-safe dependency injection, which is the foundation of the type-safe Vue library.
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!