1 slot open this monthBy the makers of shadcnregistryWe built shadcnregistry. Now we build your product.Book a callCheck availability
1 slot open this monthAI features?We build AI features into your product.Book a callCheck availability
1 slot open this month🚨 Slop police🚨 Slop police: we clean up vibe-coded apps.Report itReport your app
shadcnregistry
shadcnregistry
RegistryHealthInspect
Terms of ServicePrivacy Policy
  1. Registries
  2. pulld
  3. Fit Text

Fit Text

ui

Text shrunk to the largest whole-pixel size that still fits the box it was given, and kept fitting as the box changes. Reach for it wherever a string has to live in a space somebody else sized: the figure in a KPI tile that is usually "1.2M" and occasionally "1,284,993", a product or customer name on a card, a person's name on a name badge or a seating chart, a heading on a slide, a label in a fixed table column, a cell in a dashboard laid out on a grid, a number on a TV wallboard or kiosk nobody can scroll or hover, an org-chart node, a chart or axis label, a calendar event title in a day column, a tier name in a pricing table, an invoice line item, and any translated string that runs 40% longer in German than the design allowed for. Common asks it answers: "fit text to container react", "auto resize text to fit div", "shrink text to fit width", "scale text to fit container", "auto font size react", "responsive font size to fit box", "make text fit a fixed width div", "auto shrink font size when text is too long", "text overflows card react", "KPI number too long dashboard", "long product name breaks card layout", "fit text to width without truncating", "shrink to fit instead of ellipsis", "react-textfit", "react-fittext", "react-fit-text", "use-fit-text", "fitty react", "textfit", "FitText.js", "auto-size text react hook", "ResizeObserver font size react", "binary search font size to fit", "text fits at one width but overflows at another", "font size is wrong until the web font loads", "text shrinks then overflows again on resize", "fit text inside a flex item", "shrink text in a table cell", "文字を枠に収める react", "はみ出す文字を縮小する", "幅に合わせてフォントサイズを自動調整", "文字が長いとカードが崩れる", "自動 フォントサイズ 調整 react", "枠内に収まる最大の文字サイズ". **The version almost everybody writes first measures the string once and scales it, and that assumes glyph width is proportional to font size.** It is not. Measured on the default UI face, the same string takes 12.49px of advance per point of size at 16px and 11.43px at 64px — faces track tighter as they grow — so an estimate of `16 * (available / widthAt16)` was wrong at most of the box widths swept, in one direction throwing away four sizes that would have fitted and in the other naming a size that overflows. A registry component inherits whatever face the page is using, so neither direction can be assumed away. This does a binary search over whole pixels instead, which costs six or seven measurements over `[8, 96]` and is exact. **Whole pixels, because sub-pixel precision is below the rounding of the thing being measured.** Bisecting to a thousandth of a pixel took twelve more forced layouts, moved the answer by 0.03px, and then re-measured as 241px of content in a 240px box — one pixel worse than the integer answer that had just fitted. `scrollWidth` and `clientWidth` are rounded integers, which is also why a pixel of reported overflow is forgiven: without that tolerance a box whose text fits it exactly loses a whole step of size to a rounding artefact. **The search runs over the whole range every time and never reads its own last answer.** Nudging relative to the current size — shrink a step while it overflows, grow a step while there is room — has no fixed point: inside a ResizeObserver it oscillated between 21px and 20px for as long as it was left running, 37 callbacks in 600ms, with no error raised anywhere and a layout pass burnt every frame. A search from scratch is a fixed point, so writing the size from inside the callback settles after one extra pass instead of never. **The ceiling is re-read with this component's own size cleared, every time.** Read it without clearing and the ceiling ratchets downwards — each measurement takes the previous fitted size as its maximum and the text can never grow back when the box widens again. Clearing it also means the ceiling is whatever the stylesheet currently asks for, so `className="text-2xl md:text-5xl"` works for free: the breakpoint changes the design size, the resize that carries it re-measures, and the new ceiling is picked up without being told about. **It measures the real element, not a hidden probe span.** For a bare string a probe is a measurement of the same thing, agreeing to the exact fraction of a pixel at every size tested — but a probe is only the same box if every box property matches, and anything in `em` scales with the font. Give the element `px-[0.5em]` and a bare probe is 48px narrower at a 48px font, so the fitted size overflows by a whole em in exactly the case where fitting matters most. **A box that has not been laid out is not measured at all.** Inside a closed dialog, an unselected tab, anything `display: none`, both measurements report zero, and zero is "not measured yet" rather than "nothing fits" — shrinking on the strength of it leaves 12px text for the moment the dialog opens, which is the one state no resize comes along to correct. A web font arriving after the first measurement is waited on separately through `document.fonts.ready`, because a face swapping in changes every glyph width without changing the box and no observer reports it. **The floor is the part worth arguing about.** Shrinking without a limit always "fits", by producing text nobody can read, and a 6px label in a dashboard is a worse outcome than a clipped one — a reader can at least see that something was cut and widen or hover to get it. So `min` defaults to 12px, and at the floor this stops shrinking and clips with an ellipsis, reporting `overflowing` so the caller can do something better. It never makes text bigger: growing a short string to fill its box is a different component with a different failure mode (a two-letter name at 90px), which is why the ceiling is the design's own size rather than a number chosen here. The text itself is never touched — the same text node at a different size — so find-in-page, selection, the clipboard and the accessibility tree all get the string in full. That is the whole difference between this and the ellipsis answers to the same problem, which solve it by removing characters. `useFitFontSize` is exported on its own for a size you want without this markup (a number in a table cell, a label inside an SVG you are drawing yourself), and `onFit` is guarded on the values last reported rather than on a dependency array, so a parent that re-renders on the callback cannot turn a measurement into a loop. Theme tokens only, no hardcoded colours, no dependencies beyond React. Official shadcn/ui has nothing here, and the measurement is not close: fetching every entry in its registry today — 65 listed, 62 fetchable, code-block and questionnaire and trace 404 on both style tracks — and searching all 223,287 bytes of it, the strings fitText, scaleToFit, textFit, ResizeObserver, getBoundingClientRect, useLayoutEffect and fontSize are every one of them a zero hit. Official never measures an element and never sets a font size in code at all. Within pulld it sits beside middle-truncate and overflow-tooltip, which answer the same question by removing characters or revealing them on hover rather than by keeping all of them smaller, and opposite autosize-textarea, which grows the box to fit the text where this fits the text to the box. column-resizer and virtual-list measure boxes but never resize type.

pnpm dlx shadcn@latest add "https://pulld.pages.dev/r/fit-text.json"
Premium blocks for shadcn/uiShadcnblocks.com · AffiliateExplore
components/ui/fit-text.tsx