Input Addons and URL Fields
Decision guide for units, fixed suffixes, usernames, slugs, and responsive URL prefixes.
Choose the composition from the behavior of the static text, not from how one example domain happens to look locally. Production domains, organization paths, and enterprise usernames are not bounded.
This guide is the default for humans and agents adding or migrating an input with adjacent text.
Choose the pattern first
| Requirement | Use | Why |
|---|---|---|
| No adjacent text | Coss Input / NumberField | Do not introduce an InputGroup without a need |
| Predictable unit, currency, or fixed suffix | InputWithAddonText | The addon is bounded and remains useful at every breakpoint |
| Profile / username URL with a variable prefix | getUrlFieldAddonParts(..., { splitLongDomain: false }) + UrlInputWithAddon | Keep the complete prefix in the desktop addon |
| Conventional URL whose domain may be long | Default getUrlFieldAddonParts(...) + UrlFieldLabelRow + UrlInputWithAddon | Long domains move beside the label; the path stays in the addon |
| Generated value where copy is the primary action | CopyableField | Copy control, feedback, and value belong together |
Do not decide based on today's cal.com value. A field is variable when deployment, organization, team,
or user data can change the static text.
Bounded addons
Units, currencies, and fixed suffixes stay visible on mobile. Use align to place the addon and include
fixed punctuation in text.
Examples shipped in product:
minutes,credits, andeventsusealign="after";- currency symbols use the default
align="before"; - organization subdomains use
text=".cal.com"withalign="after".
Do not use URL splitting or a mobile URL description for these bounded values.
Variable URL fields
URL fields have one invariant across all policies: mobile shows the complete current URL below the
input. The desktop addon is hidden below sm; UrlFieldDescription restores the static prefix,
separator, and live editable value.
Normalize the static input before calling the helper:
- omit
https:///http://from display text; - remove a trailing
/; - pass path segments without leading or trailing
/; - pass the editable slug separately as
value.
Profile and username policy
Use when the field represents a profile identity and keeping the complete prefix in the desktop addon is more useful than reclaiming input width.
acme-enterprise.cal.com/team/customer-success
const urlParts = getUrlFieldAddonParts(domain, path, {
splitLongDomain: false,
});Do not add UrlFieldLabelRow for this policy: domainDisplay is intentionally null.
Conventional URL policy
Use for create, duplicate, setup, and admin flows where the editable slug needs room. The default helper
policy splits domains at URL_PREFIX_SPLIT_MIN_LENGTH while keeping the path next to the input.
acme-enterprise.cal.com/team/customer-success/15min
Always render UrlFieldLabelRow from the helper result. It renders nothing extra for short domains and
provides the correct decorative desktop domain for long ones.
Do not:
- count characters in the call site;
- slice a domain locally;
- render a second hand-written mobile label;
- manually concatenate a separate mobile description;
- put the protocol in visible addon or description text.
Separators are data
/ is the default, not an assumption. Pass separator and placement explicitly:
// cal.com/team/acme
<UrlInputWithAddon separator="/" align="before" {...props} />
// acme.cal.com
<UrlInputWithAddon separator="." align="after" {...props} />fullStaticText never includes that join separator. UrlInputWithAddon applies it consistently to the
desktop addon and the complete mobile preview.
If .cal.com is a short fixed suffix that should remain visible on mobile, it is a bounded addon and
belongs in InputWithAddonText instead.
Labels and accessibility
Every control stays inside Field.
- The real
FieldLabelnames the editable control. UrlFieldLabelRowmarks the split domainaria-hiddenbecause it is visual context, not a second label.- Use an explicit
aria-labelonly when the visible label is intentionally decorative or the control is inside a separately labelled group. - Keep status and copy controls in
endAddon; do not make the static text interactive. UrlFieldDescriptionis supporting context, not the accessible name.
Exceptional horizontal layouts
Some existing surfaces place the URL input beside actions. In those cases:
- Set
showDescription={false}onUrlInputWithAddon. - Keep the URL input inside the horizontal group.
- Render
UrlFieldDescriptionimmediately after the group. - Pass the same complete current URL: static text + separator + editable value.
This is a layout escape hatch, not the default composition.
Implementation checklist
Before opening a PR:
- Search for a real field with the same product behavior.
- Classify the addon as bounded, profile URL, conventional URL, or copy-first.
- Use
getUrlFieldAddonParts; do not add local split/truncation helpers. - Keep protocol and trailing separators out of normalized display text.
- Use one separator source for desktop and mobile.
- Verify a short domain, a domain at/above the split threshold, an empty addon, and the mobile layout.
- Keep RHF
Controller/Field/FieldErrorwiring unchanged when migrating. - Cite the product call site in docs when introducing a new variation.
Reference call sites
| Pattern | File |
|---|---|
| Bounded duration suffix | apps/web/modules/event-types/components/create-event-type-form.tsx |
Fixed .cal.com suffix | apps/web/modules/ee/organizations/components/org-profile-form-card.tsx |
| Profile URL | apps/web/modules/ee/teams/views/components/team-profile-form-card.tsx |
| Username URL | apps/web/components/ui/UsernameAvailability/UsernameTextfield.tsx |
| Conventional long-domain split | packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx |
| Horizontal action layout | apps/web/modules/settings/admin/impersonation-view.tsx |
| Copy-first generated value | apps/web/modules/ee/sso/components/ConnectionInfo.tsx |
Example URLs
Not exhaustive — open these locally from http://localhost:3000.
| Path | Pattern |
|---|---|
/event-types | Duration addon and create / duplicate URL |
/event-types/<eventTypeId>?tabName=setup | Conventional URL split |
/settings/teams/<teamId>/profile | Profile URL |
/settings/organizations/profile | Fixed organization suffix |