diff --git a/packages/ui-library/src/components/forms/text-field/text-field-styles.ts b/packages/ui-library/src/components/forms/text-field/text-field-styles.ts index 1d7c1254..1f91be4c 100644 --- a/packages/ui-library/src/components/forms/text-field/text-field-styles.ts +++ b/packages/ui-library/src/components/forms/text-field/text-field-styles.ts @@ -145,7 +145,16 @@ export const textFieldStyles = tv({ // --- Outlined variant --- { variant: 'outlined', active: true, class: { input: 'border-t-transparent', - label: '!h-auto -translate-y-1/2 pl-4', + // Use a fixed translate in rem (≈50% of the active label's + // 0.9375rem height — text-[0.75rem] × leading-tight). A percentage + // translate gets recomputed against the *current* label height, + // which jumps from 0.9375rem to the input's full height the moment + // !h-auto is removed on blur — producing a transient transform far + // larger than the floated value and a visible upward overshoot + // before the transition settles. Anchoring in rem (rather than %) + // keeps the start/end values consistent across the height swap and + // scales with the root font-size. + label: '!h-auto -translate-y-[0.5rem] pl-4', } }, { variant: 'outlined', dense: true, class: { input: 'py-2', label: 'leading-[2.5]' } }, diff --git a/packages/ui-library/src/components/forms/text-input-styles.ts b/packages/ui-library/src/components/forms/text-input-styles.ts index ba33f8fb..2c4b8a91 100644 --- a/packages/ui-library/src/components/forms/text-input-styles.ts +++ b/packages/ui-library/src/components/forms/text-input-styles.ts @@ -45,6 +45,14 @@ export const textInputBase = tv({ 'rounded pointer-events-none px-2 transition-all -mt-2', 'border border-black/[0.23]', 'dark:border-white/[0.23]', + // Force the fieldset onto its own GPU compositing layer so the + // focused border is rasterized on integer pixel boundaries. Without + // this, at fractional y-coordinates with non-integer device pixel + // ratios (e.g. DPR 1.25), Chromium anti-aliases the 1.6px-2px focus + // border across two physical pixel rows behind the floated label, + // appearing as "two thin lines crossing the label". translateZ(0) + // promotes to a layer; the layer's raster snaps to integer pixels. + 'transform-gpu', ].join(' '), legend: 'invisible text-[0.75rem] truncate [max-width:calc(100%-1rem)] leading-[0]', },