diff --git a/packages/vue/src/composables/useCounter/demo.vue b/packages/vue/src/composables/useCounter/demo.vue
new file mode 100644
index 0000000..a94d1cb
--- /dev/null
+++ b/packages/vue/src/composables/useCounter/demo.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/vue/src/composables/useCounter/index.test.ts b/packages/vue/src/composables/useCounter/index.test.ts
new file mode 100644
index 0000000..6ad2196
--- /dev/null
+++ b/packages/vue/src/composables/useCounter/index.test.ts
@@ -0,0 +1,76 @@
+import { it, expect, describe } from 'vitest';
+import { useCounter } from '.';
+import { ref } from 'vue';
+
+describe('useCounter', () => {
+ it('initialize count with the provided initial value', () => {
+ const { count } = useCounter(5);
+ expect(count.value).toBe(5);
+ });
+
+ it('initialize count with the provided initial value from a ref', () => {
+ const { count } = useCounter(ref(5));
+ expect(count.value).toBe(5);
+ });
+
+ it('increment count by 1 by default', () => {
+ const { count, increment } = useCounter(0);
+ increment();
+ expect(count.value).toBe(1);
+ });
+
+ it('increment count by the specified delta', () => {
+ const { count, increment } = useCounter(0);
+ increment(5);
+ expect(count.value).toBe(5);
+ });
+
+ it('decrement count by 1 by default', () => {
+ const { count, decrement } = useCounter(5);
+ decrement();
+ expect(count.value).toBe(4);
+ });
+
+ it('decrement count by the specified delta', () => {
+ const { count, decrement } = useCounter(10);
+ decrement(5);
+ expect(count.value).toBe(5);
+ });
+
+ it('set count to the specified value', () => {
+ const { count, set } = useCounter(0);
+ set(10);
+ expect(count.value).toBe(10);
+ });
+
+ it('get the current count value', () => {
+ const { get } = useCounter(5);
+ expect(get()).toBe(5);
+ });
+
+ it('reset count to the initial value', () => {
+ const { count, reset } = useCounter(10);
+ count.value = 5;
+ reset();
+ expect(count.value).toBe(10);
+ });
+
+ it('reset count to the specified value', () => {
+ const { count, reset } = useCounter(10);
+ count.value = 5;
+ reset(20);
+ expect(count.value).toBe(20);
+ });
+
+ it('clamp count to the minimum value', () => {
+ const { count, decrement } = useCounter(Number.MIN_SAFE_INTEGER);
+ decrement();
+ expect(count.value).toBe(Number.MIN_SAFE_INTEGER);
+ });
+
+ it('clamp count to the maximum value', () => {
+ const { count, increment } = useCounter(Number.MAX_SAFE_INTEGER);
+ increment();
+ expect(count.value).toBe(Number.MAX_SAFE_INTEGER);
+ });
+});
\ No newline at end of file
diff --git a/packages/vue/src/composables/useCounter/index.ts b/packages/vue/src/composables/useCounter/index.ts
new file mode 100644
index 0000000..3f27868
--- /dev/null
+++ b/packages/vue/src/composables/useCounter/index.ts
@@ -0,0 +1,58 @@
+import { ref, unref, type MaybeRef } from 'vue';
+import { clamp } from '@robonen/stdlib';
+
+export interface UseCounterOptions {
+ min?: number;
+ max?: number;
+}
+
+/**
+ * @name useCounter
+ * @category Utilities
+ * @description A composable that provides a counter with increment, decrement, set, get, and reset functions
+ *
+ * @param {MaybeRef} [initialValue=0] The initial value of the counter
+ * @param {UseCounterOptions} [options={}] The options for the counter
+ * @param {number} [options.min=Number.MIN_SAFE_INTEGER] The minimum value of the counter
+ * @param {number} [options.max=Number.MAX_SAFE_INTEGER] The maximum value of the counter
+ *
+ * @example
+ * const { count, increment } = useCounter(0);
+ *
+ * @example
+ * const { count, increment, decrement, set, get, reset } = useCounter(0, { min: 0, max: 10 });
+ */
+export function useCounter(initialValue: MaybeRef = 0, options: UseCounterOptions = {}) {
+ let _initialValue = unref(initialValue);
+ const count = ref(initialValue);
+
+ const {
+ min = Number.MIN_SAFE_INTEGER,
+ max = Number.MAX_SAFE_INTEGER,
+ } = options;
+
+ const increment = (delta = 1) =>
+ count.value = clamp(count.value + delta, min, max);
+
+ const decrement = (delta = 1) =>
+ count.value = clamp(count.value - delta, min, max);
+
+ const set = (value: number) =>
+ count.value = clamp(value, min, max);
+
+ const get = () => count.value;
+
+ const reset = (value = _initialValue) => {
+ _initialValue = value;
+ return set(value);
+ };
+
+ return {
+ count,
+ increment,
+ decrement,
+ set,
+ get,
+ reset,
+ };
+};
diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts
new file mode 100644
index 0000000..65eee94
--- /dev/null
+++ b/packages/vue/src/index.ts
@@ -0,0 +1 @@
+export * from './composables';
\ No newline at end of file