diff --git a/app/javascript/packages/assets/README.md b/app/javascript/packages/assets/README.md
new file mode 100644
index 00000000000..1ceefbb9399
--- /dev/null
+++ b/app/javascript/packages/assets/README.md
@@ -0,0 +1,35 @@
+# `@18f/identity-assets`
+
+Utilities for resolving asset URLs from [Ruby on Rails' Asset Pipeline](https://guides.rubyonrails.org/asset_pipeline.html).
+
+## Usage
+
+Within your code, use `getAssetPath` and provide a raw asset path, where the expected return value is the URL resolved by the Ruby on Rails Asset pipeline:
+
+```ts
+const spriteURL = getAssetPath('sprite.svg');
+```
+
+The included Webpack plugin will scan for references to `getAssetPath` and add those as assets of the associated Webpack entrypoint.
+
+```ts
+// webpack.config.js
+
+module.exports = {
+ // ...
+ plugins: [
+ // ...
+ new RailsAssetsWebpackPlugin(),
+ ],
+};
+```
+
+The expectation is that this can be used in combination with a tool like [`WebpackManifestPlugin`](https://github.com/shellscape/webpack-manifest-plugin) to generate a JSON manifest of all assets expected to be loaded with a given Webpack entrypoint, so that the backend can ensure those asset paths are populated into a `
+```
diff --git a/app/javascript/packages/config/README.md b/app/javascript/packages/config/README.md
new file mode 100644
index 00000000000..cbd08991cfe
--- /dev/null
+++ b/app/javascript/packages/config/README.md
@@ -0,0 +1,21 @@
+# `@18f/identity-config`
+
+Utilities for retrieving global application configuration values.
+
+## Usage
+
+From your JavaScript code, retrieve a configuration value using the `getConfigValue` export:
+
+```ts
+const appName = getConfigValue('appName');
+```
+
+The configuration is expected to be bootstrapped in page markup within a `
+```
diff --git a/app/javascript/packages/device/README.md b/app/javascript/packages/device/README.md
new file mode 100644
index 00000000000..4eddb5570db
--- /dev/null
+++ b/app/javascript/packages/device/README.md
@@ -0,0 +1,34 @@
+# `@18f/identity-device`
+
+Utilities for detecting details about the user's device.
+
+## Usage
+
+Import the desired utility function from the package:
+
+```ts
+import { isLikelyMobile } from '@18f/identity-device';
+
+isLikelyMobile();
+// true
+```
+
+## API
+
+### `isIPad`
+
+Returns true if the device is an iPad, or false otherwise.
+
+iPadOS devices no longer list the correct user agent. As a proxy, we check for the incorrect one (Macintosh) then test the number of touchpoints, which for iPads will be 5.
+
+### `isLikelyMobile`
+
+Returns true if the device is likely a mobile device, or false otherwise. This is a rough approximation, using device user agent sniffing.
+
+### `hasMediaAccess`
+
+Returns true if the current device allows access to camera device APIs.
+
+### `isCameraCapableMobile`
+
+Returns true if the current device is assumed to be a mobile device where a camera is available, or false otherwise. This is a rough approximation, using device user agent sniffing and availability of camera device APIs.
diff --git a/app/javascript/packages/document-capture-polling/README.md b/app/javascript/packages/document-capture-polling/README.md
new file mode 100644
index 00000000000..18f42cb180a
--- /dev/null
+++ b/app/javascript/packages/document-capture-polling/README.md
@@ -0,0 +1,20 @@
+# `@18f/identity-document-capture-polling`
+
+Package implementing behaviors associated with the hybrid handoff document capture flow, where document capture is initiated on a desktop computer and completed on a mobile device. The behaviors of this package are responsible for polling for the result of a document capture happening on another device, and redirecting the user upon completion or failure.
+
+## Usage
+
+Initialize the package's binding with the polling endpoint and required elements:
+
+```ts
+import { DocumentCapturePolling } from '@18f/identity-document-capture-polling';
+
+new DocumentCapturePolling({
+ statusEndpoint: '/path/to/endpoint',
+ elements: {
+ backLink: document.querySelector('.link-sent-back-link'),
+ form: document.querySelector('.link-sent-continue-button-form'),
+ },
+}).bind();
+```
+
diff --git a/app/javascript/packages/document-capture/README.md b/app/javascript/packages/document-capture/README.md
new file mode 100644
index 00000000000..7083f63bbcc
--- /dev/null
+++ b/app/javascript/packages/document-capture/README.md
@@ -0,0 +1,22 @@
+# `@18f/identity-document-capture`
+
+React components for displaying a user interface for a user to upload or capture photos of their state-issued identification.
+
+## Usage
+
+Render the included `` React component. Most settings are expected to be configured by wrapping the component with one or more of the included context provider components.
+
+```tsx
+import { render } from 'react-dom';
+import { DocumentCapture, AnalyticsContextProvider } from '@18f/identity-document-capture';
+
+const appRoot = document.getElementById('app-root');
+
+render(
+
+ {/* ... */}
+
+ ,
+ appRoot
+);
+```
diff --git a/app/javascript/packages/form-steps/README.md b/app/javascript/packages/form-steps/README.md
new file mode 100644
index 00000000000..4435bfac638
--- /dev/null
+++ b/app/javascript/packages/form-steps/README.md
@@ -0,0 +1,21 @@
+# `@18f/identity-form-steps`
+
+React components for managing a user's progression through a series of steps in a form.
+
+## Usage
+
+At a minimum, render the `` React component with an array of step configurations. Each step must include a `name` and `form`, where the `form` is a React component that will be rendered once the user reaches the step.
+
+```tsx
+import { render } from 'react-dom';
+import { FormSteps } from '@18f/identity-form-steps';
+
+const STEPS = [
+ { name: 'First Step', form: () =>
Welcome to the first step!
},
+ { name: 'Second Step', form: () =>
Welcome to the second step!
},
+];
+
+const appRoot = document.getElementById('app-root');
+
+render(, appRoot);
+```
diff --git a/app/javascript/packages/masked-text-toggle/README.md b/app/javascript/packages/masked-text-toggle/README.md
new file mode 100644
index 00000000000..d346c20fa18
--- /dev/null
+++ b/app/javascript/packages/masked-text-toggle/README.md
@@ -0,0 +1,32 @@
+# `@18f/identity-masked-text-toggle`
+
+Package implementing behaviors associated with toggling the visibility of text which is masked by default due to its sensitivity.
+
+For example, a Social Security number may be masked to show only `6**-**-***4` by default, and allow a user to toggle the visibility of the full number if desired.
+
+## Usage
+
+Initialize the package's binding with the polling endpoint and required elements:
+
+```ts
+import MaskedTextToggle from '@18f/identity-masked-text-toggle';
+
+const toggle = document.querySelector('.masked-text-toggle');
+new MaskedTextToggle(toggle).bind();
+```
+
+The given toggle element is expected to be a checkbox, associated with the masked text wrapper by an `aria-controls` attribute.
+
+The masked text wrapper is expected to contain two variations of the text: a masked form (with a `data-masked="true"` attribute) and a revealed form (with a `data-masked="false"` attribute). The package will toggle visibility using the `display-none` utility class from the U.S. Web Design System.
+
+```html
+
+
+ 6**-**-***4
+
+
+ 666-12-1234
+
+
+
+```
diff --git a/app/javascript/packages/memorable-date/README.md b/app/javascript/packages/memorable-date/README.md
new file mode 100644
index 00000000000..9865cc45843
--- /dev/null
+++ b/app/javascript/packages/memorable-date/README.md
@@ -0,0 +1,22 @@
+# `@18f/identity-memorable-date`
+
+Custom element implementing behaviors associated with Login.gov's adaptation of the U.S. Web Design System [Memorable Date component](https://designsystem.digital.gov/components/memorable-date/).
+
+## Usage
+
+Importing the element will register the `` custom element:
+
+```ts
+import '@18f/identity-memorable-date/memorable-date-element';
+```
+
+The custom element will implement modal behavior, but all markup must already exist.
+
+```html
+
+
+
+
+
+
+```
diff --git a/app/javascript/packages/password-confirmation/README.md b/app/javascript/packages/password-confirmation/README.md
index 5a5ae306cc8..97ad4545006 100644
--- a/app/javascript/packages/password-confirmation/README.md
+++ b/app/javascript/packages/password-confirmation/README.md
@@ -1,4 +1,4 @@
-# `@18f/password-confirmation`
+# `@18f/identity-password-confirmation`
Custom element implementation that adds password inputs with validation for confirmation.
@@ -7,7 +7,7 @@ Custom element implementation that adds password inputs with validation for conf
Importing the element will register the `` custom element:
```ts
-import '@18f/password-confirmation/password-confirmation-element';
+import '@18f/identity-password-confirmation/password-confirmation-element';
```
The custom element will implement the behavior for validation, but all markup must already exist.
diff --git a/app/javascript/packages/password-toggle/README.md b/app/javascript/packages/password-toggle/README.md
new file mode 100644
index 00000000000..84d23f0e595
--- /dev/null
+++ b/app/javascript/packages/password-toggle/README.md
@@ -0,0 +1,22 @@
+# `@18f/identity-password-toggle`
+
+Custom element implementation that toggles the visibility of a password field text in response to clicking a checkbox.
+
+## Usage
+
+Importing the element will register the `` custom element:
+
+```ts
+import '@18f/identity-password-toggle/password-toggle-element';
+```
+
+The custom element will implement associatd behaviors, but all markup must already exist.
+
+```html
+
+
+
+
+
+
+```
diff --git a/app/javascript/packages/phone-input/README.md b/app/javascript/packages/phone-input/README.md
new file mode 100644
index 00000000000..fa14a63cace
--- /dev/null
+++ b/app/javascript/packages/phone-input/README.md
@@ -0,0 +1,31 @@
+# `@18f/identity-phone-input`
+
+Custom element implementation initializes an interactive phone input, using [`intl-tel-input`](https://github.com/jackocnr/intl-tel-input).
+
+## Usage
+
+Importing the element will register the `` custom element:
+
+```ts
+import '@18f/identity-phone-input';
+```
+
+The custom element will implement associatd behaviors, but all markup must already exist.
+
+```html
+
+
+
+
+
+
+
+
+
+```
diff --git a/app/javascript/packages/spinner-button/README.md b/app/javascript/packages/spinner-button/README.md
new file mode 100644
index 00000000000..54702984562
--- /dev/null
+++ b/app/javascript/packages/spinner-button/README.md
@@ -0,0 +1,38 @@
+# `@18f/identity-spinner-button`
+
+Custom element and React component for displaying a spinner effect on a button when clicked.
+
+## Usage
+
+### Custom Element
+
+Importing the element will register the `` custom element:
+
+```ts
+import '@18f/identity-spinner-button/spinner-button-element';
+```
+
+The custom element will implement associated behaviors, but all markup must already exist, rendered server-side or by the included React component.
+
+When clicked, the a `spinner-button--spinner-active` class will be added to the root element, which can be used to control the visibility of associated elements using custom CSS styles.
+
+```html
+
+
+
+```
+
+### React
+
+The package exports a `` component.
+
+```tsx
+import { render } from 'react-dom';
+import { SpinnerButton } from '@18f/identity-spinner-button';
+
+const appRoot = document.getElementById('app-root');
+
+render(Spin!, appRoot)
+```
diff --git a/app/javascript/packages/time-element/README.md b/app/javascript/packages/time-element/README.md
new file mode 100644
index 00000000000..49e7c385778
--- /dev/null
+++ b/app/javascript/packages/time-element/README.md
@@ -0,0 +1,21 @@
+# `@18f/identity-time-element`
+
+Custom element which converts a time string rendered by the server into the user's local time.
+
+## Usage
+
+The package exports a `TimeElement` custom element, which can be registered using the browser's custom element registry:
+
+```ts
+import { TimeElement } from '@18f/identity-time-element';
+
+customElements.define('lg-time', TimeElement);
+```
+
+The custom element will implement associated behaviors, but all markup must already exist.
+
+```html
+
+ September 26, 2023 at 4:20 PM
+
+```
diff --git a/app/javascript/packages/validated-field/README.md b/app/javascript/packages/validated-field/README.md
new file mode 100644
index 00000000000..e8fc974b5ab
--- /dev/null
+++ b/app/javascript/packages/validated-field/README.md
@@ -0,0 +1,45 @@
+# `@18f/identity-validated-field`
+
+Custom element and React component for controlling validation behavior associated with a form input.
+
+It enhances the behavior of an input by:
+
+- Displaying an error message on the page when form submission results in a validation error
+- Moving focus to the first invalid field when form submission results in a validation error
+- Providing default error messages for common validation constraints (e.g. required field missing)
+- Allowing you to customize error messages associated with default field validation
+- Resetting the error state when an input value changes
+
+## Usage
+
+### Custom Element
+
+Importing the element will register the `` custom element:
+
+```ts
+import '@18f/identity-validated-field/validated-field-element';
+```
+
+The custom element will implement associated behaviors, but all markup must already exist, rendered server-side or by the included React component.
+
+```html
+
+
+
+
+
+
+```
+
+### React
+
+The package exports a `` component. If rendered without a child, it will render a text input by default.
+
+```tsx
+import { render } from 'react-dom';
+import { ValidatedField } from '@18f/identity-validated-field';
+
+const appRoot = document.getElementById('app-root');
+
+render(, appRoot);
+```
diff --git a/scripts/validate-workspaces.js b/scripts/validate-workspaces.js
index 8b90e52ed60..a9a75bc42d1 100755
--- a/scripts/validate-workspaces.js
+++ b/scripts/validate-workspaces.js
@@ -135,22 +135,6 @@ const EXCEPTIONS = {
// Reason: ESLint plugins must follow a specific format for their package names, which conflicts
// with our standard "identity-" prefix.
checkHaveCorrectPackageName: ['app/javascript/packages/eslint-plugin/package.json'],
- // Reason: There is no reason aside from legacy prior to enforcement. Please write documentation!
- checkHaveDocumentation: [
- 'app/javascript/packages/assets/package.json',
- 'app/javascript/packages/config/package.json',
- 'app/javascript/packages/device/package.json',
- 'app/javascript/packages/document-capture/package.json',
- 'app/javascript/packages/document-capture-polling/package.json',
- 'app/javascript/packages/form-steps/package.json',
- 'app/javascript/packages/masked-text-toggle/package.json',
- 'app/javascript/packages/memorable-date/package.json',
- 'app/javascript/packages/password-toggle/package.json',
- 'app/javascript/packages/phone-input/package.json',
- 'app/javascript/packages/spinner-button/package.json',
- 'app/javascript/packages/time-element/package.json',
- 'app/javascript/packages/validated-field/package.json',
- ],
};
const manifestPaths = glob('app/javascript/packages/*/package.json');