diff --git a/docs/v3-plan.md b/docs/v3-plan.md
new file mode 100644
index 000000000..e47d8d8f1
--- /dev/null
+++ b/docs/v3-plan.md
@@ -0,0 +1,265 @@
+# Core-Styles v3 — Work Plan
+
+## Branch / PR Map
+
+```
+main ◄── epic/v3 PR #454 (Step 3 — last to merge)
+ ▲
+ ├── epic/v3--reorg PR #455 (Step 1)
+ └── epic/v3--gh-345-…figma PR #456 (Step 2)
+```
+
+| PR | Title | Branch → Base | Link |
+|----|-------|---------------|------|
+| #454 | refactor: v3 | `epic/v3` → `main` | https://github.com/TACC/Core-Styles/pull/454 |
+| #455 | refactor(v3): reorganize styles per client library | `epic/v3--reorg` → `epic/v3` | https://github.com/TACC/Core-Styles/pull/455 |
+| #456 | feat(v3): use re-brand colors from figma | `epic/v3--gh-345-tokens-from-figma` → `epic/v3` | https://github.com/TACC/Core-Styles/pull/456 |
+
+---
+
+## Execution Order
+
+Steps 1 and 2 touch different files and can run in parallel.
+Step 3 must come last.
+
+---
+
+## PR #455 — Reorganize Styles per Client Library
+
+**Branch:** `epic/v3--reorg` **Base:** `epic/v3`
+
+### What it does
+
+Moves every pattern into a per-library subdirectory that mirrors the client
+library's own organization. Each pattern gets its own named folder containing
+its source, template, config, and demo styles — co-located.
+
+**Reference: a finished item** (`tacc/components/c-button/`):
+
+```
+tacc/components/c-button/
+ c-button.postcss ← source (moved inside the subdir, not flat)
+ c-button--cms.postcss ← variant source (also inside subdir)
+ c-button.hbs ← Fractal template
+ c-button.selectors.postcss
+ config.yml ← includes context.subdir (see Step B below)
+ demo.css ← demo-context styles
+```
+
+### Done ✅
+
+| Category | Status |
+|----------|--------|
+| Bootstrap 3 | ✅ fully reorganized |
+| Bootstrap 4 | ✅ fully reorganized |
+| Bootstrap 5 | ✅ fully reorganized |
+| Django CMS variants (`djangocms-*`) | ✅ moved to own dirs |
+| Docs libs (`mkdocs`, `pymdownx`, `python-markdown`, `readthedocs`, `tacc-docs`) | ✅ moved |
+| TACC / `components/` | ✅ fully reorganized |
+
+### Remaining ❌
+
+#### 1. TACC sub-libraries — per-item reorganization
+
+Six sub-libraries still need each of their patterns to be reorganized.
+Apply the same 3-step pattern used for `tacc/components/` to each item in:
+
+| Sub-library | Path |
+|-------------|------|
+| elements | `src/lib/_imports/tacc/elements/` |
+| generics | `src/lib/_imports/tacc/generics/` |
+| objects | `src/lib/_imports/tacc/objects/` |
+| settings | `src/lib/_imports/tacc/settings/` |
+| tools | `src/lib/_imports/tacc/tools/` |
+| trumps | `src/lib/_imports/tacc/trumps/` |
+
+**The 3-step pattern per item** (where `{lib}` = e.g. `elements`, `{item}` = e.g. `links`):
+
+**Step A — Move source into subdir**
+```
+tacc/{lib}/{item}.postcss → tacc/{lib}/{item}/{item}.postcss
+tacc/{lib}/{item}--variant.postcss → tacc/{lib}/{item}/{item}--variant.postcss
+tacc/{lib}/{item}.selectors.postcss → tacc/{lib}/{item}/{item}.selectors.postcss
+```
+(All `.postcss` files that belong to this item move together.)
+
+**Step B — Add `context.subdir` to the item's `config.yml`**
+```yaml
+context:
+ subdir: "tacc/{lib}/{item}"
+```
+If the item does not yet have a `config.yml`, create one with at minimum:
+```yaml
+context:
+ subdir: "tacc/{lib}/{item}"
+```
+
+**Step C — Remove the `(CMS|…)` prefix from the Fractal label**
+
+In `config.yml` or variant `label:` fields:
+```yaml
+# Before:
+label: (CMS) Links
+# After:
+label: Links
+```
+
+> **Note:** Some patterns have multiple `.postcss` files (e.g. `root.postcss`,
+> `root--cms.postcss`, `root--portal.postcss`). All variants belong inside the
+> same subdir alongside the main file. The subdir name should be the
+> "base name" without the variant suffix (e.g. `root/`).
+
+> **Note:** Some patterns are partially reorganized — a subdirectory already
+> exists with some files (`config.yml`, `.hbs`, `readme.md`) but the source
+> `.postcss` is still flat at the parent level. Treat these the same way:
+> move the flat `.postcss` (and any variants) into the existing subdir and
+> complete Steps B and C as normal.
+
+> **Note:** Some items (e.g. `_elements.config.yml`, `demo.postcss`,
+> `README.css`, `selectors.*.postcss`) are infrastructure files, not patterns.
+> Do not create subdirs for them.
+
+#### 2. Quality-of-life — absolute imports
+
+- **Support absolute imports:** Update the PostCSS config / build tooling so
+ `@import` paths can be written relative to `src/lib/_imports/` (or repo root)
+ rather than relative to the current file.
+- **Use absolute imports:** Once supported, rewrite all relative `@import`
+ paths across all `.postcss` source files to use absolute paths.
+
+ This makes future reorganization much safer (no broken relative paths).
+
+#### 3. UX — Fractal sidebar grouping
+
+Group related libraries in the Fractal demo sidebar:
+
+| Current labels | Target group label |
+|----------------|-------------------|
+| Bootstrap 3, Bootstrap 4, Bootstrap 5 | **Bootstrap** |
+| Django CMS Blog, Django CMS Forms, Django CMS Picture | **Django CMS** |
+| MkDocs, PyMdown, Python-Markdown, ReadTheDocs, TACC Docs | **Styles for Docs** |
+
+This is configured in `fractality.config.js` and/or the relevant `config.yml`
+files.
+
+### Testing
+
+1. `npm start` — launch the Fractal dev server
+2. Open every pattern in the sidebar — confirm all render correctly
+3. Verify sidebar organization mirrors the client library's own docs:
+ - [Bootstrap 3](https://getbootstrap.com/docs/3.4/)
+ - [Bootstrap 4](https://getbootstrap.com/docs/4.6/)
+ - [Bootstrap 5](https://getbootstrap.com/docs/5.3/)
+ - [TACC v2 reference](https://github.com/TACC/Core-Styles/tree/v2.39.0/src/lib/_imports)
+4. No regressions — styles should look identical to before the reorg
+
+---
+
+## PR #456 — Use Re-brand Colors from Figma
+
+**Branch:** `epic/v3--gh-345-tokens-from-figma` **Base:** `epic/v3`
+**Label:** paused **Completes:** [issue #457](https://github.com/TACC/Core-Styles/issues/457)
+
+### What it does
+
+Adds CSS custom-property design tokens from TACC's Figma re-brand. All token
+files live under `src/v3/variables/`.
+
+**New files:**
+```
+src/v3/variables/tokens/
+ primitives-designsafe.css ← concrete color values for DesignSafe
+ primitives-tacc-cms.css ← concrete color values for TACC CMS
+ primitives-tacc-portal.css ← concrete color values for TACC Portal
+ tokens-dark.css ← semantic aliases (dark theme)
+ tokens-light.css ← semantic aliases (light theme)
+src/v3/variables/
+ designsafe.dark.css / designsafe.light.css
+ tacc-cms.dark.css / tacc-cms.light.css
+ tacc-portal.dark.css / tacc-portal.light.css
+```
+
+**Token structure (two layers):**
+- **Primitives** — concrete values per product
+ e.g. `--color-traffic-red-normal: #e03535`
+- **Semantics** — intent-to-primitive aliases
+ e.g. `--color-danger-normal: var(--color-traffic-red-normal)`
+
+### Remaining ❌
+
+> **Blocker — human required:** Task 1 below requires access to Figma and
+> cannot be completed autonomously. All other tasks can proceed independently,
+> but the PR should not be marked ready for review until task 1 is resolved.
+
+1. **Confirm token values** _(human required — needs Figma access)_ — verify
+ the manually-entered values in `src/v3/variables/tokens/primitives-*.css`
+ are current against the latest TACC re-brand spec in Figma.
+2. **Verify build integration** — confirm the token files are imported by the
+ main build entry points in `epic/v3` (they currently live under `src/v3/`,
+ which may or may not be included in the main build yet).
+3. **Fill in the Testing section** of the PR description (currently `1. …`).
+4. **Remove the `paused` label** and mark as ready for review.
+
+### Testing
+
+- Build the full stylesheet (`npm run build`) and confirm no errors.
+- Open a pattern in Fractal that uses a semantic color variable (e.g. a danger
+ state) and confirm the re-brand color is applied.
+
+---
+
+## PR #454 — Refactor: v3 (Epic / Umbrella)
+
+**Branch:** `epic/v3` **Base:** `main`
+
+### What it does
+
+This is the base branch all other v3 work merges into. Key changes already in place:
+
+| Change | Detail |
+|--------|--------|
+| `.css` → `.postcss` | All source files renamed; built `.css` output is never confused with source |
+| No committed `/dist` | Build output is generated-only; never committed |
+| `bin/build-each.js` | Builds individual stylesheets in place |
+| `bin/only-commit-source.js` | Lint guard: errors if compiled `.css` is found inside `src/` — fixed and passing as of PR #604 |
+| `3.0.0-alpha.0` | Version bump |
+
+**Backwards-compatibility:**
+- Styles: ✅ backwards-compatible
+- Directory structure of `/dist`: ❌ breaking change
+- Source file extensions (`.postcss`): ❌ breaking change
+
+### Remaining ❌
+
+1. **Wait for #455 and #456** to be merged into `epic/v3`.
+2. **Sync** `epic/v3` with `main` one final time before merging.
+3. **Fill in** the PR description's `Changes`, `Testing`, and `UI` sections.
+4. **Verify full build** passes: `npm run build && npm run lint`.
+5. **Remove draft status** and request review.
+
+### Testing
+
+```sh
+npm run lint # must pass (no compiled .css in src/)
+npm run build # must complete without errors
+npm start # demo server must start and show all patterns
+```
+
+---
+
+## Notes
+
+### The `only-commit-source.js` guard
+
+Runs as `prepublishOnly` and via `npm run lint`. Passes on `epic/v3` and
+`epic/v3--reorg` as of PR #604. After any merge from `main`, run
+`npm run lint` — new `.css` source files from `main` may need renaming to
+`.postcss`.
+
+### Ghost directories on `epic/v3--reorg`
+
+`src/lib/_imports/objects/o-offset-content/` has a `config.yml`, `.hbs`, and
+`readme.md` (added by a recent main merge) but no `.postcss` source. The
+source lives at `tacc/objects/o-offset-content.postcss`. This directory is a
+leftover from the pre-reorg state and should be cleaned up as part of the
+objects reorganization in PR #455.
diff --git a/src/lib/_imports/bootstrap4/components/btn/btn.hbs b/src/lib/_imports/bootstrap4/components/btn/btn.hbs
index 4b8e60d1e..3b573d40b 100644
--- a/src/lib/_imports/bootstrap4/components/btn/btn.hbs
+++ b/src/lib/_imports/bootstrap4/components/btn/btn.hbs
@@ -9,18 +9,18 @@
class="btn btn-{{type}}{{#if size}} btn-{{size}}{{/if}}{{#if active}} active{{/if}}"
{{#if disabled}}disabled{{/if}}
>
- {{#if hasIconBefore}}{{/if}}
+ {{#if hasIconBefore}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{text}}
- {{#if hasIconAfter}}{{/if}}
+ {{#if hasIconAfter}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{else}}
- {{#if hasIconBefore}}{{/if}}
+ {{#if hasIconBefore}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{text}}
- {{#if hasIconAfter}}{{/if}}
+ {{#if hasIconAfter}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{/unless}}
diff --git a/src/lib/_imports/bootstrap4/utilities/color-text.css b/src/lib/_imports/bootstrap4/utilities/color-text.css
index 1a77c9bfd..1f3b5b5ab 100644
--- a/src/lib/_imports/bootstrap4/utilities/color-text.css
+++ b/src/lib/_imports/bootstrap4/utilities/color-text.css
@@ -43,7 +43,7 @@ a.text-primary:is(
:hover,
:focus
) {
- color: var(--global-color-accent--normal) !important;
+ color: var(--global-color-accent--light) !important;
}
a.text-secondary:is(
:link,
@@ -62,5 +62,5 @@ a[class*="text-"]:not(
:hover,
:focus
) {
- color: var(--global-color-accent--normal) !important;
+ color: var(--global-color-accent--light) !important;
}
diff --git a/src/lib/_imports/bootstrap4/utilities/color-text/color-text.hbs b/src/lib/_imports/bootstrap4/utilities/color-text/color-text.hbs
index 25789666b..9bf263cfe 100644
--- a/src/lib/_imports/bootstrap4/utilities/color-text/color-text.hbs
+++ b/src/lib/_imports/bootstrap4/utilities/color-text/color-text.hbs
@@ -7,9 +7,9 @@
class="text-{{type}}{{#if size}} text-{{size}}{{/if}}"
{{#if disabled}}disabled{{/if}}
>
- {{#if hasIconBefore}}{{/if}}
+ {{#if hasIconBefore}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{text}}
- {{#if hasIconAfter}}{{/if}}
+ {{#if hasIconAfter}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{#if asLink}}a{{else}}p{{/if}}>
{{/types}}
diff --git a/src/lib/_imports/components/bootstrap.modal.postcss b/src/lib/_imports/components/bootstrap.modal.postcss
index 509f18b65..5f4c6cad1 100644
--- a/src/lib/_imports/components/bootstrap.modal.postcss
+++ b/src/lib/_imports/components/bootstrap.modal.postcss
@@ -42,7 +42,6 @@
/* To darken close button */
.modal-header .close {
- color: var(--global-color-primary--x-dark);
opacity: 1;
}
/* FAQ: The specificity matches Bootstrap */
diff --git a/src/lib/_imports/components/c-app-card/c-app-card.hbs b/src/lib/_imports/components/c-app-card/c-app-card.hbs
index 1804540ac..d765c277e 100644
--- a/src/lib/_imports/components/c-app-card/c-app-card.hbs
+++ b/src/lib/_imports/components/c-app-card/c-app-card.hbs
@@ -8,7 +8,7 @@
{{/if}}
- {{#if icon}}{{/if}}
+ {{#if icon}}Meaningful text or add [aria-hidden="true"]{{/if}}
{{!-- FAQ: These custom icons are not available in Cortal --}}
{{!-- {{#if icon}}{{/if}} --}}
{{title}}
diff --git a/src/lib/_imports/components/c-feed-list/c-feed-list.hbs b/src/lib/_imports/components/c-feed-list/c-feed-list.hbs
index 30116a248..126463408 100644
--- a/src/lib/_imports/components/c-feed-list/c-feed-list.hbs
+++ b/src/lib/_imports/components/c-feed-list/c-feed-list.hbs
@@ -13,7 +13,7 @@