diff --git a/apps/desktop/src/app/cron/index.tsx b/apps/desktop/src/app/cron/index.tsx
index dbddcb01a..d195453e1 100644
--- a/apps/desktop/src/app/cron/index.tsx
+++ b/apps/desktop/src/app/cron/index.tsx
@@ -768,7 +768,7 @@ function CronEditorDialog({
onEdit(field.key, event.target.value)}
placeholder={field.is_set ? field.redacted_value || 'Replace current value' : copy.placeholder}
diff --git a/apps/desktop/src/app/overlays/overlay-search-input.tsx b/apps/desktop/src/app/overlays/overlay-search-input.tsx
index ab3603d4d..5241c358d 100644
--- a/apps/desktop/src/app/overlays/overlay-search-input.tsx
+++ b/apps/desktop/src/app/overlays/overlay-search-input.tsx
@@ -37,7 +37,7 @@ export function OverlaySearchInput({
)
}
diff --git a/apps/desktop/src/app/settings/config-settings.tsx b/apps/desktop/src/app/settings/config-settings.tsx
index 3969c2c09..957531e9b 100644
--- a/apps/desktop/src/app/settings/config-settings.tsx
+++ b/apps/desktop/src/app/settings/config-settings.tsx
@@ -89,7 +89,7 @@ function ConfigField({
if (schema.type === 'number') {
return row(
{
const raw = e.target.value
const n = raw === '' ? 0 : Number(raw)
@@ -108,7 +108,7 @@ function ConfigField({
if (schema.type === 'list') {
return row(
onChange(
e.target.value
@@ -154,7 +154,7 @@ function ConfigField({
/>
) : (
onChange(e.target.value)}
placeholder="Not set"
value={String(value ?? '')}
diff --git a/apps/desktop/src/app/settings/constants.ts b/apps/desktop/src/app/settings/constants.ts
index 51f44dcc7..4738dad65 100644
--- a/apps/desktop/src/app/settings/constants.ts
+++ b/apps/desktop/src/app/settings/constants.ts
@@ -22,7 +22,7 @@ interface ProviderPrefix {
}
export const EMPTY_SELECT_VALUE = '__hermes_empty__'
-export const CONTROL_TEXT = 'text-[0.8125rem]'
+export const CONTROL_TEXT = 'text-xs'
export const PROVIDER_GROUPS: ProviderPrefix[] = [
{ prefix: 'NOUS_', name: 'Nous Portal', priority: 0 },
diff --git a/apps/desktop/src/components/ui/control.ts b/apps/desktop/src/components/ui/control.ts
new file mode 100644
index 000000000..c4d272109
--- /dev/null
+++ b/apps/desktop/src/components/ui/control.ts
@@ -0,0 +1,24 @@
+import { cva, type VariantProps } from 'class-variance-authority'
+
+// Single source of truth for non-composer form-control chrome — Input,
+// Textarea, and SelectTrigger all consume this. Mirrors `buttonVariants`:
+// 2.5px radius, 12px text, padding-driven sizing (no fixed heights). The visual
+// chrome (background, border tint, hover, focus glow, invalid state) comes from
+// the `desktop-input-chrome` CSS so every control shares one exact look.
+export const controlVariants = cva(
+ 'desktop-input-chrome w-full min-w-0 rounded-[2.5px] border text-xs leading-4 text-foreground outline-none placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
+ {
+ variants: {
+ size: {
+ sm: 'px-2 py-1',
+ default: 'px-2.5 py-1.5',
+ lg: 'px-3 py-2 text-sm leading-5'
+ }
+ },
+ defaultVariants: {
+ size: 'default'
+ }
+ }
+)
+
+export type ControlVariantProps = VariantProps
diff --git a/apps/desktop/src/components/ui/input.tsx b/apps/desktop/src/components/ui/input.tsx
index ddb8de6b2..726b26187 100644
--- a/apps/desktop/src/components/ui/input.tsx
+++ b/apps/desktop/src/components/ui/input.tsx
@@ -2,11 +2,19 @@ import * as React from 'react'
import { cn } from '@/lib/utils'
-function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
+import { type ControlVariantProps, controlVariants } from './control'
+
+function Input({
+ className,
+ type,
+ size,
+ ...props
+}: Omit, 'size'> & ControlVariantProps) {
return (
) {
return
}
-function SelectTrigger({ className, children, ...props }: React.ComponentProps) {
+function SelectTrigger({
+ className,
+ children,
+ size,
+ ...props
+}: React.ComponentProps & ControlVariantProps) {
return (
) {
+import { type ControlVariantProps, controlVariants } from './control'
+
+function Textarea({ className, size, ...props }: React.ComponentProps<'textarea'> & ControlVariantProps) {
return (