prime-ui-kit 0.21.3 → 0.21.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/chunks/{chunk-JO7E6SZQ.js → chunk-PFASPQSJ.js} +365 -337
  2. package/dist/chunks/{chunk-R5LI5BO3.js → chunk-UNNQITFE.js} +11 -6
  3. package/dist/chunks/{chunk-BAJRN3QN.js → chunk-WGXXPJDR.js} +4 -1
  4. package/dist/color-picker.js +2 -2
  5. package/dist/components/input/Input.d.ts +1 -1
  6. package/dist/components/login-form/LoginForm.d.ts +5 -1
  7. package/dist/components/textarea/Textarea.d.ts +1 -1
  8. package/dist/index.js +2 -2
  9. package/dist/internal/autofill.d.ts +30 -0
  10. package/dist/skill/checklist.md +2 -0
  11. package/dist/skill/reference/components/button/examples/in-form.tsx +0 -1
  12. package/dist/skill/reference/components/drawer/examples/screen.tsx +1 -5
  13. package/dist/skill/reference/components/input/COMPONENT.md +3 -0
  14. package/dist/skill/reference/components/input/examples/in-form.tsx +0 -1
  15. package/dist/skill/reference/components/input/examples/validation.tsx +1 -1
  16. package/dist/skill/reference/components/login-form/COMPONENT.md +2 -2
  17. package/dist/skill/reference/components/login-form/examples/sizes.tsx +6 -2
  18. package/dist/skill/reference/components/tabs/examples/orientation.tsx +1 -1
  19. package/dist/skill/reference/components/textarea/COMPONENT.md +2 -0
  20. package/dist/skill/ux.md +19 -2
  21. package/package.json +1 -1
  22. package/src/components/button/examples/in-form.tsx +0 -1
  23. package/src/components/drawer/examples/screen.tsx +1 -5
  24. package/src/components/input/COMPONENT.md +3 -0
  25. package/src/components/input/examples/in-form.tsx +0 -1
  26. package/src/components/input/examples/validation.tsx +1 -1
  27. package/src/components/login-form/COMPONENT.md +2 -2
  28. package/src/components/login-form/examples/sizes.tsx +6 -2
  29. package/src/components/tabs/examples/orientation.tsx +1 -1
  30. package/src/components/textarea/COMPONENT.md +2 -0
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import {
4
+ AUTOFILL_OFF,
4
5
  AnchorRectProvider,
6
+ AutofillScope,
5
7
  Avatar,
6
8
  Badge,
7
9
  Button,
@@ -138,6 +140,7 @@ import {
138
140
  touchTargetClass,
139
141
  touchTargetRowClass,
140
142
  useAnchorName,
143
+ useAutofill,
141
144
  useBalancedColumns,
142
145
  useChipTier,
143
146
  useControllableState,
@@ -183,7 +186,7 @@ import {
183
186
  useTextParts,
184
187
  visuallyHiddenClass,
185
188
  zonedParts
186
- } from "./chunk-JO7E6SZQ.js";
189
+ } from "./chunk-PFASPQSJ.js";
187
190
  import {
188
191
  COMPACT_QUERY,
189
192
  ControlSizeProvider,
@@ -2969,6 +2972,7 @@ function FooterField({
2969
2972
  className: cx(fieldInputTextClass, Datepicker_default.fieldTime),
2970
2973
  value: time,
2971
2974
  inputMode: "numeric",
2975
+ ...AUTOFILL_OFF,
2972
2976
  maxLength: 5,
2973
2977
  onChange: (e) => onTime(e.target.value)
2974
2978
  }
@@ -3850,7 +3854,6 @@ function SmartFilterToolbar({
3850
3854
  Input.Field,
3851
3855
  {
3852
3856
  type: "search",
3853
- autoComplete: "off",
3854
3857
  "aria-label": labels.searchPlaceholder,
3855
3858
  placeholder: labels.searchPlaceholder,
3856
3859
  value: search,
@@ -4103,7 +4106,6 @@ function NumberRange({
4103
4106
  Input.Field,
4104
4107
  {
4105
4108
  inputMode: "decimal",
4106
- autoComplete: "off",
4107
4109
  "aria-label": formatLabel(side === "from" ? labels.fromLabel : labels.toLabel, {
4108
4110
  field: field.label
4109
4111
  }),
@@ -19935,7 +19937,6 @@ function CustomColorField() {
19935
19937
  {
19936
19938
  value: draft,
19937
19939
  placeholder: "#12A594",
19938
- autoComplete: "off",
19939
19940
  spellCheck: false,
19940
19941
  onValueChange: (next) => {
19941
19942
  setDraft(next);
@@ -20237,7 +20238,7 @@ function CommandMenuInput({
20237
20238
  "aria-controls": listboxId,
20238
20239
  "aria-activedescendant": activeId ? optionId(activeId) : void 0,
20239
20240
  "aria-busy": loading || loadingMore || void 0,
20240
- autoComplete: "off",
20241
+ ...AUTOFILL_OFF,
20241
20242
  autoCorrect: "off",
20242
20243
  spellCheck: false,
20243
20244
  "data-autofocus": "",
@@ -21982,6 +21983,7 @@ function TextareaRoot({
21982
21983
  disabled: disabledProp,
21983
21984
  readOnly,
21984
21985
  value,
21986
+ autoComplete,
21985
21987
  onInput,
21986
21988
  onChange,
21987
21989
  onValueChange,
@@ -22002,6 +22004,7 @@ function TextareaRoot({
22002
22004
  const disabled = useInheritedDisabled(disabledProp);
22003
22005
  const ids = useFieldFrame(id, { label, hint, error, invalid, required }, ariaDescribedBy);
22004
22006
  const labels = useLabels(TEXTAREA_LABELS, labelsProp);
22007
+ const autofill = useAutofill(autoComplete);
22005
22008
  const innerRef = React67.useRef(null);
22006
22009
  const setRefs = useMergedRefs(innerRef, ref);
22007
22010
  const mirrorRef = React67.useRef(null);
@@ -22061,6 +22064,7 @@ function TextareaRoot({
22061
22064
  "aria-invalid": ids.invalid || void 0,
22062
22065
  "aria-describedby": ids.describedBy,
22063
22066
  "aria-busy": commit.state === "pending" || void 0,
22067
+ ...autofill,
22064
22068
  value,
22065
22069
  onInput: (event) => {
22066
22070
  if (autoResize && mirrorRef.current) {
@@ -30161,7 +30165,7 @@ function LoginFormBody({ className, children, ...rest }) {
30161
30165
  }
30162
30166
  LoginFormBody.displayName = "LoginForm.Body";
30163
30167
  function LoginFormForm({ className, children, ref, ...rest }) {
30164
- return /* @__PURE__ */ jsx81("form", { ref, className: cx(LoginForm_default.form, className), ...rest, children });
30168
+ return /* @__PURE__ */ jsx81("form", { ref, className: cx(LoginForm_default.form, className), ...rest, children: /* @__PURE__ */ jsx81(AutofillScope, { children }) });
30165
30169
  }
30166
30170
  LoginFormForm.displayName = "LoginForm.Form";
30167
30171
  function LoginFormActions({ className, children, ...rest }) {
@@ -33420,6 +33424,7 @@ function TagSelect({
33420
33424
  "aria-expanded": shown,
33421
33425
  "aria-controls": shown ? listboxId : void 0,
33422
33426
  "aria-autocomplete": "list",
33427
+ ...AUTOFILL_OFF,
33423
33428
  "aria-label": ariaLabel,
33424
33429
  "aria-labelledby": ariaLabelledBy,
33425
33430
  "aria-invalid": ids.invalid || void 0,
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import {
4
+ AUTOFILL_OFF,
4
5
  Button,
5
6
  ColorSwatches,
6
7
  FieldActionButton,
@@ -23,7 +24,7 @@ import {
23
24
  useInheritedDisabled,
24
25
  useLabels,
25
26
  useOpenState
26
- } from "./chunk-JO7E6SZQ.js";
27
+ } from "./chunk-PFASPQSJ.js";
27
28
  import {
28
29
  cx,
29
30
  useHostTier
@@ -176,6 +177,7 @@ function ChannelField({
176
177
  /* @__PURE__ */ jsx(
177
178
  "input",
178
179
  {
180
+ ...AUTOFILL_OFF,
179
181
  ref: inputRef,
180
182
  "aria-label": labels[channel],
181
183
  className: cx(fieldInputTextClass, ColorPicker_default.channelInput),
@@ -200,6 +202,7 @@ function StripHexField() {
200
202
  children: /* @__PURE__ */ jsx(
201
203
  "input",
202
204
  {
205
+ ...AUTOFILL_OFF,
203
206
  ref: inputRef,
204
207
  "aria-label": labels.hex,
205
208
  autoCapitalize: "off",
@@ -4,8 +4,8 @@ import {
4
4
  ColorField,
5
5
  ColorPicker,
6
6
  parseColor
7
- } from "./chunks/chunk-BAJRN3QN.js";
8
- import "./chunks/chunk-JO7E6SZQ.js";
7
+ } from "./chunks/chunk-WGXXPJDR.js";
8
+ import "./chunks/chunk-PFASPQSJ.js";
9
9
  import "./chunks/chunk-HMLQCKRL.js";
10
10
  export {
11
11
  ColorField,
@@ -151,7 +151,7 @@ export type InputFieldProps = Omit<React.InputHTMLAttributes<HTMLInputElement>,
151
151
  ref?: React.Ref<HTMLInputElement>;
152
152
  };
153
153
  /** The `<input>`; its id comes from `Input.Root` so the label, hint and error stay linked. */
154
- declare function InputField({ className, "aria-describedby": ariaDescribedBy, required, disabled: disabledProp, type, onChange, onValueChange, onFocus, onBlur, onKeyDown, onClick, onInvalid, ref, ...rest }: InputFieldProps): import("react/jsx-runtime").JSX.Element;
154
+ declare function InputField({ className, "aria-describedby": ariaDescribedBy, required, disabled: disabledProp, type, autoComplete, onChange, onValueChange, onFocus, onBlur, onKeyDown, onClick, onInvalid, ref, ...rest }: InputFieldProps): import("react/jsx-runtime").JSX.Element;
155
155
  declare namespace InputField {
156
156
  var displayName: string;
157
157
  }
@@ -124,7 +124,11 @@ export type LoginFormFormProps = {
124
124
  children?: React.ReactNode;
125
125
  ref?: React.Ref<HTMLFormElement>;
126
126
  } & React.FormHTMLAttributes<HTMLFormElement>;
127
- /** The `<form>`: fields, then the submit button, one column with field → field spacing. Native submit and `onSubmit` work as usual. */
127
+ /**
128
+ * The `<form>`: fields, then the submit button, one column with field → field spacing. Native
129
+ * submit and `onSubmit` work as usual. Autofill is the point of a sign-in form: a kit field inside
130
+ * it without an `autoComplete` token is left to the browser and the password managers.
131
+ */
128
132
  declare function LoginFormForm({ className, children, ref, ...rest }: LoginFormFormProps): import("react/jsx-runtime").JSX.Element;
129
133
  declare namespace LoginFormForm {
130
134
  var displayName: string;
@@ -100,7 +100,7 @@ export type TextareaRootProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaEl
100
100
  * A multi-line field. `className` goes to the field frame, `id`, `ref` and the native attributes
101
101
  * to the `<textarea>` (it has no `Field` part).
102
102
  */
103
- declare function TextareaRoot({ id, className, size: sizeProp, invalid, focusRing, label, required, optional, hint, hintTone, error, counter, reserveSupportRow, autoResize, rows, maxRows, disabled: disabledProp, readOnly, value, onInput, onChange, onValueChange, onFocus, onBlur, onKeyDown, onSelect, onCommit, formatting, labels: labelsProp, "aria-describedby": ariaDescribedBy, children, ref, ...rest }: TextareaRootProps): import("react/jsx-runtime").JSX.Element;
103
+ declare function TextareaRoot({ id, className, size: sizeProp, invalid, focusRing, label, required, optional, hint, hintTone, error, counter, reserveSupportRow, autoResize, rows, maxRows, disabled: disabledProp, readOnly, value, autoComplete, onInput, onChange, onValueChange, onFocus, onBlur, onKeyDown, onSelect, onCommit, formatting, labels: labelsProp, "aria-describedby": ariaDescribedBy, children, ref, ...rest }: TextareaRootProps): import("react/jsx-runtime").JSX.Element;
104
104
  declare namespace TextareaRoot {
105
105
  var displayName: string;
106
106
  }
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ import {
90
90
  useSidebar,
91
91
  useTokenValue,
92
92
  useUploadQueue
93
- } from "./chunks/chunk-R5LI5BO3.js";
93
+ } from "./chunks/chunk-UNNQITFE.js";
94
94
  import {
95
95
  Avatar,
96
96
  Badge,
@@ -125,7 +125,7 @@ import {
125
125
  tierMappings,
126
126
  tones,
127
127
  variants
128
- } from "./chunks/chunk-JO7E6SZQ.js";
128
+ } from "./chunks/chunk-PFASPQSJ.js";
129
129
  import {
130
130
  ControlSizeProvider,
131
131
  useOptionalControlSize
@@ -0,0 +1,30 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Browser autofill and password managers stay off a kit text field: a product screen is full of
4
+ * searches, filters and record fields that a saved login or address has no business filling.
5
+ * `autocomplete="off"` turns off the browser's own suggestions where it is honoured (Chrome and
6
+ * Firefox history and address autofill on desktop); browsers still read login-looking fields on
7
+ * their own, and the password-manager extensions read only their own opt-outs: 1Password
8
+ * `data-1p-ignore`, LastPass `data-lpignore`, Bitwarden `data-bwignore`, Dashlane
9
+ * `data-form-type="other"`.
10
+ */
11
+ export declare const AUTOFILL_OFF: {
12
+ readonly autoComplete: "off";
13
+ readonly "data-1p-ignore": "true";
14
+ readonly "data-lpignore": "true";
15
+ readonly "data-bwignore": "true";
16
+ readonly "data-form-type": "other";
17
+ };
18
+ type AutofillProps = {
19
+ autoComplete?: string;
20
+ } & Partial<Omit<typeof AUTOFILL_OFF, "autoComplete">>;
21
+ export declare function AutofillScope({ children }: {
22
+ children: React.ReactNode;
23
+ }): import("react/jsx-runtime").JSX.Element;
24
+ /**
25
+ * The autofill attributes of a text field. A consumer's `autoComplete` token other than `"off"`
26
+ * opts in: the token goes through and the opt-outs are dropped. Without a token the field is off,
27
+ * except inside `AutofillScope`, where the browser and the password managers decide.
28
+ */
29
+ export declare function useAutofill(autoComplete: string | undefined): AutofillProps;
30
+ export {};
@@ -39,6 +39,8 @@ checks.
39
39
  - [ ] A request a value starts — a simulated one too — is cancelled when the value changes again ([nothing to guess](ux.md#6-nothing-to-guess)).
40
40
  - [ ] Tabs, filters, search, page and the open record are in the URL ([nothing to guess](ux.md#6-nothing-to-guess)).
41
41
  - [ ] Defaults prefill what the system knows; each field has the control and `inputMode` of its data ([ask less](ux.md#7-ask-less)).
42
+ - [ ] `autoComplete` tokens only on sign-in and sign-up fields (`LoginForm.Form`, a password change); no token on a search or a record field ([ask less](ux.md#7-ask-less)).
43
+ - [ ] No search, filter or record field (other than a record's e-mail) says «username», «login», «email», «логин», «почта» in its placeholder, label, `name` or `id` — Safari offers saved logins there ([ask less](ux.md#7-ask-less)).
42
44
  - [ ] I looked at the screen in a browser, filled badly ([the visual check](ux.md#9-look-at-it-before-handing-it-over)).
43
45
 
44
46
  ## Grid and spacing
@@ -32,7 +32,6 @@ export default function ButtonInFormExample() {
32
32
  type="email"
33
33
  name="email"
34
34
  placeholder="name@company.ru"
35
- autoComplete="email"
36
35
  onValueChange={() => setError(undefined)}
37
36
  />
38
37
  </Input.Wrapper>
@@ -180,11 +180,7 @@ export default function DrawerScreenExample() {
180
180
  </Radio.Group>
181
181
  <Input.Root label="Название" error={errors.name}>
182
182
  <Input.Wrapper>
183
- <Input.Field
184
- name="name"
185
- placeholder="ООО «Ромашка»"
186
- autoComplete="organization"
187
- />
183
+ <Input.Field name="name" placeholder="ООО «Ромашка»" />
188
184
  </Input.Wrapper>
189
185
  </Input.Root>
190
186
  <div className={styles.pair}>
@@ -96,6 +96,7 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
96
96
  | `onValueChange` | `(value: string) => void` | — | Called with the new string; native `onChange` still fires first. |
97
97
  | `aria-describedby` | `string` | — | Merged with the affix and hint/error ids from the root. |
98
98
  | `required` | `boolean` | — | Overrides the root's `required` for the native input. |
99
+ | `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
99
100
  | `…rest` | `Omit<InputHTMLAttributes<HTMLInputElement>, "size" \| "id">` | — | `value`, `defaultValue`, `onChange`, `type`, `disabled`, `readOnly`, `maxLength`, `placeholder`… |
100
101
 
101
102
  ### Input.Icon
@@ -272,6 +273,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
272
273
  - `minStrength` uses native constraint validation: below the level the input's `validationMessage` is `labels.strengthTooWeak`, the form refuses to submit, and the message becomes the field's error (`aria-invalid`).
273
274
  - `Input.Icon` is decorative (`aria-hidden`). `Input.Affix` and `Input.InlineAffix` are `aria-hidden` in the reading order but describe the input (`aria-describedby`, before the hint): the unit is heard with the field, so the label never repeats it. An `aria-label` on the affix is said instead of its text («рублей» for `₽`).
274
275
  - `suggestions` make the input a `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls` on the open `listbox`, which the label names); the highlight is `aria-activedescendant`, so focus never leaves the field.
276
+ - Autofill is off unless you pass `autoComplete`: without a token the input carries `autocomplete="off"` and the opt-outs of 1Password, LastPass, Bitwarden and Dashlane (`data-1p-ignore`, `data-lpignore`, `data-bwignore`, `data-form-type="other"`); a token passes through and drops them. With `suggestions` it stays off. Inside `LoginForm.Form` a field without a token is left to the browser.
275
277
  - A `Select` inside `Input.Wrapper` is the box's start segment: its trigger is named by the select root's `aria-label` and the picked value («Иконка: Звезда»).
276
278
  - `Input.ClearButton` is a real button named by `labels.clear`, with `aria-controls` on the input.
277
279
  - `Input.Action` and `Input.CopyButton` are buttons (or a link with `asChild`) named by their label, with a tooltip; copying announces `labels.copied` through a polite live region.
@@ -338,6 +340,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
338
340
  - A Select and an Input side by side for one value (an icon + a name) → the Select inside `Input.Wrapper`, named by its `aria-label`.
339
341
  - A text field with a hand-made time mask, or a Datepicker for a time of day → `Input.Field type="time"`.
340
342
  - Returning `false` from `onCommit` to refuse → reject the Promise (throw) and set `error`.
343
+ - `autoComplete="email"` / `"name"` on a search, filter or record field → leave it out: autofill is off unless you pass `autoComplete`; tokens belong on sign-in and sign-up fields (`LoginForm.Form`) and on a new password (`new-password`).
341
344
 
342
345
  ## Related
343
346
  - **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments, the `valid` check), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
@@ -40,7 +40,6 @@ export default function InputInFormExample() {
40
40
  name="name"
41
41
  onValueChange={() => edit("name")}
42
42
  defaultValue="ООО «Северный ветер»"
43
- autoComplete="organization"
44
43
  />
45
44
  </Input.Wrapper>
46
45
  </Input.Root>
@@ -28,7 +28,7 @@ export default function InputValidationExample() {
28
28
  <>
29
29
  <Input.Root label="Название организации" required hint="Как в учредительных документах">
30
30
  <Input.Wrapper>
31
- <Input.Field defaultValue="ООО «Северный ветер»" autoComplete="organization" />
31
+ <Input.Field defaultValue="ООО «Северный ветер»" />
32
32
  </Input.Wrapper>
33
33
  </Input.Root>
34
34
  <Input.Root
@@ -99,7 +99,7 @@ Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, divid
99
99
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | The other div attributes. |
100
100
 
101
101
  ### LoginForm.Form
102
- `ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props.
102
+ `ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props. The one place autofill stays on: a kit field inside it without `autoComplete` is left to the browser and the password managers.
103
103
 
104
104
  | Prop | Type | Default | Description |
105
105
  |---|---|---|---|
@@ -184,7 +184,7 @@ Root carries `data-size`, `data-align` and `data-flat`.
184
184
  - The title is a real heading (`h1` by default) and the form is a native `<form>`: Enter submits.
185
185
  - The logo is decorative; with an `aria-label` it is the product mark image (`role="img"`).
186
186
  - Give `LoginForm.Form` an `aria-label` (or `aria-labelledby` pointing to the title id) so the form is a landmark.
187
- - Fields use `autoComplete` (`email`, `current-password`, `new-password`, `one-time-code`) so password managers and OTP autofill work.
187
+ - Every field names its purpose with `autoComplete` — `email` or `username`, `current-password` to sign in, `new-password` to sign up, reset or accept an invite, `one-time-code` for a code (`DigitInput` sets it) — so password managers fill and save the right field. Kit fields elsewhere keep autofill off.
188
188
  - Field errors come from the fields (`aria-invalid`, described by the error). A refused sign-in has no Banner: the e-mail takes `invalid` and the `error` of the password says why, announced by the support row of the field.
189
189
 
190
190
  ### Labels
@@ -16,12 +16,16 @@ export default function LoginFormSizesExample() {
16
16
  <LoginForm.Form onSubmit={(e) => e.preventDefault()}>
17
17
  <Input.Root label="Email">
18
18
  <Input.Wrapper>
19
- <Input.Field type="email" placeholder="name@company.ru" />
19
+ <Input.Field type="email" autoComplete="email" placeholder="name@company.ru" />
20
20
  </Input.Wrapper>
21
21
  </Input.Root>
22
22
  <Input.Root label="Пароль">
23
23
  <Input.Wrapper>
24
- <Input.Field type="password" placeholder="********" />
24
+ <Input.Field
25
+ type="password"
26
+ autoComplete="current-password"
27
+ placeholder="********"
28
+ />
25
29
  </Input.Wrapper>
26
30
  </Input.Root>
27
31
  <Button.Root type="submit" fullWidth>
@@ -38,7 +38,7 @@ export default function TabsOrientationExample() {
38
38
  <div className={styles.form}>
39
39
  <Input.Root label="Имя и фамилия">
40
40
  <Input.Wrapper>
41
- <Input.Field defaultValue="Анна Смирнова" autoComplete="name" />
41
+ <Input.Field defaultValue="Анна Смирнова" />
42
42
  </Input.Wrapper>
43
43
  </Input.Root>
44
44
  <Input.Root label="Должность">
@@ -60,6 +60,7 @@ Unlike Input, Textarea has no Wrapper / Field parts: `Textarea.Root` renders the
60
60
  | `aria-describedby` | `string` | — | Merged before the hint/error ids. |
61
61
  | `onCommit` | `(value: string) => void \| Promise<void>` | — | Inline save: fires with a changed value on blur and Mod+Enter; Escape reverts. Return a Promise: the field shows a spinner and stays editable; a rejection restores the value — show why through `error`. |
62
62
  | `formatting` | `boolean` | `false` | Selecting text shows a floating toolbar of eight Markdown tools and enables their hotkeys; a tool wraps the selection, a second press unwraps it. Off: plain text. |
63
+ | `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
63
64
  | `children` | `ReactNode` | — | Actions in the field's top end corner: `Textarea.CopyButton`, `Textarea.Action`. |
64
65
  | `labels` | `Partial<TextareaLabels>` | — | Built-in strings, see Labels. |
65
66
  | `className` | `string` | — | Class on the field frame `<div>` (label, field box, support row), as on every field root. |
@@ -226,6 +227,7 @@ Inline save (`onCommit`): the text at focus is the committed one; blur and Mod+E
226
227
  - A copy `Button` under a read-only result → `Textarea.CopyButton` as a child of the root.
227
228
  - Other children than `Textarea.CopyButton` / `Textarea.Action` → the root renders the textarea itself; children are only the corner actions.
228
229
  - `formatting` on text that is shown as plain text → the reader sees `**` and `||`; turn it on only where the text is rendered with its Markdown.
230
+ - Expecting the browser to offer saved addresses in a note → autofill is off unless you pass `autoComplete` (a token such as `street-address` opts in).
229
231
 
230
232
  ## Related
231
233
  - **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (corner actions, formatting tools), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [Button](../button/COMPONENT.md) and [Kbd](../kbd/COMPONENT.md) (formatting tools), Icon (`format.*`); `internal/textFormatting` + `internal/textFormattingToolbar` (the formatting shared with Composer), [VisuallyHidden](../visually-hidden/COMPONENT.md)
package/dist/skill/ux.md CHANGED
@@ -256,8 +256,25 @@ Which button starts each step and how the confirm behaves:
256
256
  person's own company, the last used currency. An empty required field the person always fills the
257
257
  same way is a missing default.
258
258
  - **The right control for the data:** `inputMode="numeric" | "decimal" | "tel" | "email"`,
259
- `autoComplete` for personal data, `Datepicker` for a date, `Select` for a closed list. A free text
260
- field for a closed set of values is a bug.
259
+ `Datepicker` for a date, `Select` for a closed list. A free text field for a closed set of values
260
+ is a bug.
261
+ - **Autofill only where a person signs in:** `autoComplete` tokens go on sign-in and sign-up fields
262
+ (`email`, `current-password`, `new-password`, `one-time-code`) and on a new password; everywhere
263
+ else the kit keeps autofill off, so a saved login never drops over a search or a record field.
264
+ - **Safari decides by words, not by attributes:** it ignores `autocomplete="off"` on a field it
265
+ takes for a login, and it takes a text field for one when its placeholder, label, `name` or `id`
266
+ speaks of a login: «username», «user», «login», «email», «e-mail», «логин», «почта», «имя
267
+ пользователя». The kit's opt-outs stop Chrome, Firefox and the password-manager extensions, not
268
+ Safari; on a field that is not a sign-in only the words do. So a search, a filter or a record
269
+ field never names itself with those words — it says what is searched in the domain's words:
270
+ - bad: a search `placeholder="Название или @username"`, `name="login"` on a filter, «Поиск по
271
+ email» → Safari offers the saved login over the table;
272
+ - good: `placeholder="Название или @ник"`, «Имя или контакт», «Клиент, телефон или ИНН»; no
273
+ `name` / `id` on a search at all.
274
+ A record's own e-mail field (a client's, an employee's) keeps its label «Почта» and
275
+ `type="email"`: Safari may offer a contact card there, which is right for e-mail. Never fight the
276
+ browser with tricks — a readonly-until-focus field, a hidden decoy, a made-up `autoComplete`
277
+ token: they break the field for assistive tech and stop working with the next Safari.
261
278
  - **Progressive disclosure:** the common 80% first; rare settings behind an `Accordion` item or a
262
279
  «Дополнительно» section; a long create flow in steps (`Stepper`).
263
280
  - **Group by meaning, order by use:** fields in the order a person has the information (company →
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prime-ui-kit",
3
- "version": "0.21.3",
3
+ "version": "0.21.4",
4
4
  "packageManager": "bun@1.3.13",
5
5
  "description": "React 19 UI kit for product interfaces: 79 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y, Next.js App Router ready. Ships COMPONENT.md docs, copyable examples and an agent skill.",
6
6
  "bin": {
@@ -32,7 +32,6 @@ export default function ButtonInFormExample() {
32
32
  type="email"
33
33
  name="email"
34
34
  placeholder="name@company.ru"
35
- autoComplete="email"
36
35
  onValueChange={() => setError(undefined)}
37
36
  />
38
37
  </Input.Wrapper>
@@ -180,11 +180,7 @@ export default function DrawerScreenExample() {
180
180
  </Radio.Group>
181
181
  <Input.Root label="Название" error={errors.name}>
182
182
  <Input.Wrapper>
183
- <Input.Field
184
- name="name"
185
- placeholder="ООО «Ромашка»"
186
- autoComplete="organization"
187
- />
183
+ <Input.Field name="name" placeholder="ООО «Ромашка»" />
188
184
  </Input.Wrapper>
189
185
  </Input.Root>
190
186
  <div className={styles.pair}>
@@ -96,6 +96,7 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
96
96
  | `onValueChange` | `(value: string) => void` | — | Called with the new string; native `onChange` still fires first. |
97
97
  | `aria-describedby` | `string` | — | Merged with the affix and hint/error ids from the root. |
98
98
  | `required` | `boolean` | — | Overrides the root's `required` for the native input. |
99
+ | `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
99
100
  | `…rest` | `Omit<InputHTMLAttributes<HTMLInputElement>, "size" \| "id">` | — | `value`, `defaultValue`, `onChange`, `type`, `disabled`, `readOnly`, `maxLength`, `placeholder`… |
100
101
 
101
102
  ### Input.Icon
@@ -272,6 +273,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
272
273
  - `minStrength` uses native constraint validation: below the level the input's `validationMessage` is `labels.strengthTooWeak`, the form refuses to submit, and the message becomes the field's error (`aria-invalid`).
273
274
  - `Input.Icon` is decorative (`aria-hidden`). `Input.Affix` and `Input.InlineAffix` are `aria-hidden` in the reading order but describe the input (`aria-describedby`, before the hint): the unit is heard with the field, so the label never repeats it. An `aria-label` on the affix is said instead of its text («рублей» for `₽`).
274
275
  - `suggestions` make the input a `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls` on the open `listbox`, which the label names); the highlight is `aria-activedescendant`, so focus never leaves the field.
276
+ - Autofill is off unless you pass `autoComplete`: without a token the input carries `autocomplete="off"` and the opt-outs of 1Password, LastPass, Bitwarden and Dashlane (`data-1p-ignore`, `data-lpignore`, `data-bwignore`, `data-form-type="other"`); a token passes through and drops them. With `suggestions` it stays off. Inside `LoginForm.Form` a field without a token is left to the browser.
275
277
  - A `Select` inside `Input.Wrapper` is the box's start segment: its trigger is named by the select root's `aria-label` and the picked value («Иконка: Звезда»).
276
278
  - `Input.ClearButton` is a real button named by `labels.clear`, with `aria-controls` on the input.
277
279
  - `Input.Action` and `Input.CopyButton` are buttons (or a link with `asChild`) named by their label, with a tooltip; copying announces `labels.copied` through a polite live region.
@@ -338,6 +340,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
338
340
  - A Select and an Input side by side for one value (an icon + a name) → the Select inside `Input.Wrapper`, named by its `aria-label`.
339
341
  - A text field with a hand-made time mask, or a Datepicker for a time of day → `Input.Field type="time"`.
340
342
  - Returning `false` from `onCommit` to refuse → reject the Promise (throw) and set `error`.
343
+ - `autoComplete="email"` / `"name"` on a search, filter or record field → leave it out: autofill is off unless you pass `autoComplete`; tokens belong on sign-in and sign-up fields (`LoginForm.Form`) and on a new password (`new-password`).
341
344
 
342
345
  ## Related
343
346
  - **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments, the `valid` check), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
@@ -40,7 +40,6 @@ export default function InputInFormExample() {
40
40
  name="name"
41
41
  onValueChange={() => edit("name")}
42
42
  defaultValue="ООО «Северный ветер»"
43
- autoComplete="organization"
44
43
  />
45
44
  </Input.Wrapper>
46
45
  </Input.Root>
@@ -28,7 +28,7 @@ export default function InputValidationExample() {
28
28
  <>
29
29
  <Input.Root label="Название организации" required hint="Как в учредительных документах">
30
30
  <Input.Wrapper>
31
- <Input.Field defaultValue="ООО «Северный ветер»" autoComplete="organization" />
31
+ <Input.Field defaultValue="ООО «Северный ветер»" />
32
32
  </Input.Wrapper>
33
33
  </Input.Root>
34
34
  <Input.Root
@@ -99,7 +99,7 @@ Every part is optional except `Root`, `Title` and `Form`. Fields, buttons, divid
99
99
  | `…rest` | `HTMLAttributes<HTMLDivElement>` | — | The other div attributes. |
100
100
 
101
101
  ### LoginForm.Form
102
- `ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props.
102
+ `ref` → `HTMLFormElement`. The `<form>`: fields, then the submit button, with the field → field gap of the tier. Native form props. The one place autofill stays on: a kit field inside it without `autoComplete` is left to the browser and the password managers.
103
103
 
104
104
  | Prop | Type | Default | Description |
105
105
  |---|---|---|---|
@@ -184,7 +184,7 @@ Root carries `data-size`, `data-align` and `data-flat`.
184
184
  - The title is a real heading (`h1` by default) and the form is a native `<form>`: Enter submits.
185
185
  - The logo is decorative; with an `aria-label` it is the product mark image (`role="img"`).
186
186
  - Give `LoginForm.Form` an `aria-label` (or `aria-labelledby` pointing to the title id) so the form is a landmark.
187
- - Fields use `autoComplete` (`email`, `current-password`, `new-password`, `one-time-code`) so password managers and OTP autofill work.
187
+ - Every field names its purpose with `autoComplete` — `email` or `username`, `current-password` to sign in, `new-password` to sign up, reset or accept an invite, `one-time-code` for a code (`DigitInput` sets it) — so password managers fill and save the right field. Kit fields elsewhere keep autofill off.
188
188
  - Field errors come from the fields (`aria-invalid`, described by the error). A refused sign-in has no Banner: the e-mail takes `invalid` and the `error` of the password says why, announced by the support row of the field.
189
189
 
190
190
  ### Labels
@@ -16,12 +16,16 @@ export default function LoginFormSizesExample() {
16
16
  <LoginForm.Form onSubmit={(e) => e.preventDefault()}>
17
17
  <Input.Root label="Email">
18
18
  <Input.Wrapper>
19
- <Input.Field type="email" placeholder="name@company.ru" />
19
+ <Input.Field type="email" autoComplete="email" placeholder="name@company.ru" />
20
20
  </Input.Wrapper>
21
21
  </Input.Root>
22
22
  <Input.Root label="Пароль">
23
23
  <Input.Wrapper>
24
- <Input.Field type="password" placeholder="********" />
24
+ <Input.Field
25
+ type="password"
26
+ autoComplete="current-password"
27
+ placeholder="********"
28
+ />
25
29
  </Input.Wrapper>
26
30
  </Input.Root>
27
31
  <Button.Root type="submit" fullWidth>
@@ -38,7 +38,7 @@ export default function TabsOrientationExample() {
38
38
  <div className={styles.form}>
39
39
  <Input.Root label="Имя и фамилия">
40
40
  <Input.Wrapper>
41
- <Input.Field defaultValue="Анна Смирнова" autoComplete="name" />
41
+ <Input.Field defaultValue="Анна Смирнова" />
42
42
  </Input.Wrapper>
43
43
  </Input.Root>
44
44
  <Input.Root label="Должность">
@@ -60,6 +60,7 @@ Unlike Input, Textarea has no Wrapper / Field parts: `Textarea.Root` renders the
60
60
  | `aria-describedby` | `string` | — | Merged before the hint/error ids. |
61
61
  | `onCommit` | `(value: string) => void \| Promise<void>` | — | Inline save: fires with a changed value on blur and Mod+Enter; Escape reverts. Return a Promise: the field shows a spinner and stays editable; a rejection restores the value — show why through `error`. |
62
62
  | `formatting` | `boolean` | `false` | Selecting text shows a floating toolbar of eight Markdown tools and enables their hotkeys; a tool wraps the selection, a second press unwraps it. Off: plain text. |
63
+ | `autoComplete` | `string` | `"off"` with the password-manager opt-outs; inside `LoginForm.Form`, left to the browser | A token opts in to autofill and drops the password-manager opt-outs; without one the field stays off. Pass one only on sign-in and sign-up fields and for a new password (`new-password`). |
63
64
  | `children` | `ReactNode` | — | Actions in the field's top end corner: `Textarea.CopyButton`, `Textarea.Action`. |
64
65
  | `labels` | `Partial<TextareaLabels>` | — | Built-in strings, see Labels. |
65
66
  | `className` | `string` | — | Class on the field frame `<div>` (label, field box, support row), as on every field root. |
@@ -226,6 +227,7 @@ Inline save (`onCommit`): the text at focus is the committed one; blur and Mod+E
226
227
  - A copy `Button` under a read-only result → `Textarea.CopyButton` as a child of the root.
227
228
  - Other children than `Textarea.CopyButton` / `Textarea.Action` → the root renders the textarea itself; children are only the corner actions.
228
229
  - `formatting` on text that is shown as plain text → the reader sees `**` and `||`; turn it on only where the text is rendered with its Markdown.
230
+ - Expecting the browser to offer saved addresses in a note → autofill is off unless you pass `autoComplete` (a token such as `street-address` opts in).
229
231
 
230
232
  ## Related
231
233
  - **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (corner actions, formatting tools), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [Button](../button/COMPONENT.md) and [Kbd](../kbd/COMPONENT.md) (formatting tools), Icon (`format.*`); `internal/textFormatting` + `internal/textFormattingToolbar` (the formatting shared with Composer), [VisuallyHidden](../visually-hidden/COMPONENT.md)