diff --git a/packages/frontend/src/components/ui/field.tsx b/packages/frontend/src/components/ui/field.tsx
index b51e75a..588c9b2 100644
--- a/packages/frontend/src/components/ui/field.tsx
+++ b/packages/frontend/src/components/ui/field.tsx
@@ -13,6 +13,7 @@ export function Field({
error,
tabIndex,
spellcheck,
+ maxLength,
}: {
label: string;
value?: string;
@@ -24,6 +25,7 @@ export function Field({
error?: string;
tabIndex?: number;
spellcheck?: boolean;
+ maxLength?: number;
}) {
const [internalTouched, setInternalTouched] = useState(false);
const isTouched = submitAttempted || internalTouched;
@@ -56,6 +58,7 @@ export function Field({
type={hidden ? "password" : "text"}
tabIndex={tabIndex}
spellCheck={spellcheck}
+ maxLength={maxLength}
/>
{error || invalidMessage !== "" ? (
diff --git a/packages/frontend/src/components/ui/input.tsx b/packages/frontend/src/components/ui/input.tsx
index 983aff6..997e2ab 100644
--- a/packages/frontend/src/components/ui/input.tsx
+++ b/packages/frontend/src/components/ui/input.tsx
@@ -3,18 +3,64 @@ import type * as React from "react";
import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined;
+ const currentLength = typeof props.value === "string" ? props.value.length : undefined;
+
+ const showCounter = typeof maxLength === "number" && maxLength > 0 && typeof currentLength === "number";
+
+ if (!showCounter) {
+ return (
+
+ );
+ }
+
+ const counterPercent = currentLength / maxLength;
+
return (
-
+ >
+
+ = 1
+ ? "text-destructive"
+ : counterPercent >= 0.8
+ ? "text-amber-500"
+ : "text-muted-foreground",
+ )}
+ >
+ {currentLength}/{maxLength}
+
+
);
}