Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/themeable-auth-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@seamless-auth/react': minor
---

feat(styles): make the built-in auth UI themeable with CSS custom properties

Every colour in the bundled screens now reads from a `--seamless-*` custom property with the previous
literal as its fallback, so consumers can match the auth UI to their brand by setting variables on
`:root` or on any ancestor of `<AuthRoutes />`.

This is opt-in and non-breaking. Applications that set nothing render exactly as before.
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,69 @@ Two paths are unchanged because they are owned by contracts outside this package
- `/oauth/callback` is registered with OAuth providers as an allowed redirect URI, so renaming it
would break configured integrations.

## Theming The Built-In UI

Every colour in the built-in screens is a CSS custom property with a fallback, so the default look is
unchanged if you set nothing. To match your brand, set the tokens you care about on `:root`, or on any
element that wraps `<AuthRoutes />`.

```css
:root {
--seamless-accent: #1f3a34;
--seamless-accent-hover: #16302b;
--seamless-surface: #f7f5f0;
--seamless-text: #1a1a1a;
}
```

Scoping to a wrapper also works, which is useful when the auth screens should look different from the
rest of the app:

```css
.auth-shell {
--seamless-accent: #1f3a34;
--seamless-accent-soft: #3c6f63;
}
```

There is no provider prop or JavaScript API for this. Setting the variables is the whole interface.

### Tokens

| Token | Used for | Default |
| ---------------------------- | ---------------------------------------------------------------------- | --------------------------------------------- |
| `--seamless-accent` | Primary buttons, focus rings, selected states, accent borders, spinner | `#2563eb` fills, `#3b82f6` rings and borders |
| `--seamless-accent-hover` | Hover state of primary buttons | `#1d4ed8` |
| `--seamless-accent-contrast` | Label text on accent-filled buttons | `white` |
| `--seamless-accent-soft` | Links, toggle buttons, accent icons | `#60a5fa`, `#a5b4fc` on the magic-link screen |
| `--seamless-surface` | Card and modal backgrounds | `#1f2937` |
| `--seamless-surface-raised` | Inputs and panels sitting on a card | `#374151`, `#4b5563` on the login form |
| `--seamless-surface-hover` | Hover state of secondary buttons | `#4b5563` |
| `--seamless-border` | Input, button, and panel borders | `#4b5563`, `#d1d5db` |
| `--seamless-text` | Headings and body text | `white` |
| `--seamless-text-muted` | Labels, helper text, secondary copy | `#9ca3af`, `#d1d5db` |
| `--seamless-disabled` | Background of disabled submit buttons | `#9ca3af` |
| `--seamless-danger` | Error messages | `#f87171` |
| `--seamless-success` | Success messages and the verified check icon | `#34d399` |
| `--seamless-warning` | OTP countdown and resend timers | `#facc15` |
| `--seamless-overlay` | Modal backdrop scrim | `rgba(0, 0, 0, 0.45)` |
| `--seamless-shadow` | Card and modal shadow colour | `rgba(0, 0, 0, 0.1)` to `rgba(0, 0, 0, 0.4)` |

### Notes

- Where the original design used near-duplicate shades for the same role, each declaration keeps its
own original value as the fallback. That is why some rows list more than one default. Nothing shifts
until you set the token, and once you do, every use of that role picks up your value.
- `--seamless-shadow` is the shadow colour only. Offsets and blur are fixed. Setting it applies one
colour to every shadow in the built-in UI, replacing the per-screen alpha values.
- If you set `--seamless-surface` to a light colour, set `--seamless-text` too. The default text
colour is white and will disappear otherwise.
- Two decorative tints are deliberately not tokenised: the pulse ring behind the magic-link mail icon
and the disc behind the success check. Both are translucent and sit directly under an icon, so an
opaque override would hide the icon it is meant to frame.
- The package ships one palette and no `prefers-color-scheme` rules. If you want the auth UI to follow
the system theme, wrap your own overrides in a media query.

## Backend Expectations

This package assumes a Seamless Auth-compatible backend with the auth adapter mounted at `/auth`.
Expand Down
18 changes: 9 additions & 9 deletions src/styles/deviceNameModal.module.css
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
background: var(--seamless-overlay, rgba(0, 0, 0, 0.45));
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}

.modal {
background: #1f2937;
color: #fff;
background: var(--seamless-surface, #1f2937);
color: var(--seamless-text, #fff);
padding: 24px;
border-radius: 12px;
width: 100%;
max-width: 420px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
box-shadow: 0 20px 40px var(--seamless-shadow, rgba(0, 0, 0, 0.15));
}

.description {
font-size: 14px;
color: #fff;
color: var(--seamless-text, #fff);
margin-bottom: 16px;
}

.input {
width: 100%;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #ddd;
border: 1px solid var(--seamless-border, #ddd);
margin-bottom: 20px;
font-size: 14px;
}
Expand All @@ -40,8 +40,8 @@
}

.primary {
background: #2563eb;
color: white;
background: var(--seamless-accent, #2563eb);
color: var(--seamless-accent-contrast, white);
border: none;
padding: 8px 14px;
border-radius: 8px;
Expand All @@ -50,7 +50,7 @@

.secondary {
background: transparent;
border: 1px solid #ccc;
border: 1px solid var(--seamless-border, #ccc);
padding: 8px 14px;
border-radius: 8px;
cursor: pointer;
Expand Down
62 changes: 31 additions & 31 deletions src/styles/login.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
display: flex;
align-items: center;
justify-content: center;
color: white;
color: var(--seamless-text, white);
}

.card {
background-color: #1f2937;
background-color: var(--seamless-surface, #1f2937);
padding: 2rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 6px var(--seamless-shadow, rgba(0, 0, 0, 0.1));
width: 100%;
max-width: 28rem;
}
Expand All @@ -19,11 +19,11 @@
font-weight: bold;
margin-bottom: 1.5rem;
text-align: center;
color: white;
color: var(--seamless-text, white);
}

.message {
color: #68d391;
color: var(--seamless-success, #68d391);
text-align: center;
}

Expand All @@ -33,38 +33,38 @@

.label {
display: block;
color: #d1d5db;
color: var(--seamless-text-muted, #d1d5db);
}

.input {
width: 100%;
padding: 0.5rem;
background-color: #4b5563;
border: 1px solid #d1d5db;
background-color: var(--seamless-surface-raised, #4b5563);
border: 1px solid var(--seamless-border, #d1d5db);
border-radius: 0.375rem;
margin-top: 0.25rem;
}

.input:focus {
outline: none;
box-shadow: 0 0 0 2px #3b82f6;
box-shadow: 0 0 0 2px var(--seamless-accent, #3b82f6);
}

.helperText {
font-size: 0.75rem;
color: #9ca3af;
color: var(--seamless-text-muted, #9ca3af);
margin-top: 0.5rem;
}

.error {
color: #f87171;
color: var(--seamless-danger, #f87171);
font-size: 0.875rem;
}

.button {
width: 100%;
background-color: #2563eb;
color: white;
background-color: var(--seamless-accent, #2563eb);
color: var(--seamless-accent-contrast, white);
padding: 0.5rem;
border-radius: 0.375rem;
transition: background-color 0.3s;
Expand All @@ -73,19 +73,19 @@
}

.button:hover {
background-color: #1d4ed8;
background-color: var(--seamless-accent-hover, #1d4ed8);
}

.button:disabled {
background-color: #9ca3af;
background-color: var(--seamless-disabled, #9ca3af);
cursor: not-allowed;
}

.toggle {
margin-top: 1.5rem;
width: 100%;
font-size: 0.875rem;
color: #60a5fa;
color: var(--seamless-accent-soft, #60a5fa);
text-align: center;
background: none;
border: none;
Expand All @@ -107,16 +107,16 @@
width: 100%;
padding: 0.5rem;
border-radius: 0.375rem;
background-color: #4b5563;
border: 1px solid #d1d5db;
background-color: var(--seamless-surface-raised, #4b5563);
border: 1px solid var(--seamless-border, #d1d5db);
}

.phoneInput {
width: 100%;
padding: 0.5rem;
border-radius: 0.375rem;
background-color: #4b5563;
border: 1px solid #d1d5db;
background-color: var(--seamless-surface-raised, #4b5563);
border: 1px solid var(--seamless-border, #d1d5db);
}

@media (max-width: 500px) {
Expand All @@ -131,8 +131,8 @@
margin-top: 18px;
padding: 18px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--seamless-surface-raised, rgba(255, 255, 255, 0.04));
border: 1px solid var(--seamless-border, rgba(255, 255, 255, 0.08));
display: flex;
flex-direction: column;
gap: 14px;
Expand All @@ -141,12 +141,12 @@
.fallbackHeader {
font-size: 0.95rem;
font-weight: 600;
color: #f3f4f6;
color: var(--seamless-text, #f3f4f6);
}

.fallbackDescription {
font-size: 0.85rem;
color: #9ca3af;
color: var(--seamless-text-muted, #9ca3af);
line-height: 1.4;
}

Expand All @@ -164,35 +164,35 @@
padding: 12px 14px;
border-radius: 8px;

background: rgba(0, 0, 0, 0.25);
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--seamless-surface, rgba(0, 0, 0, 0.25));
border: 1px solid var(--seamless-border, rgba(255, 255, 255, 0.08));

cursor: pointer;
transition: all 0.15s ease;
}

.fallbackActionButton:hover {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.15);
background: var(--seamless-surface-hover, rgba(255, 255, 255, 0.06));
border-color: var(--seamless-border, rgba(255, 255, 255, 0.15));
}

.actionTitle {
font-size: 0.9rem;
font-weight: 500;
color: #f9fafb;
color: var(--seamless-text, #f9fafb);
}

.actionSubtext {
font-size: 0.75rem;
color: #9ca3af;
color: var(--seamless-text-muted, #9ca3af);
margin-top: 2px;
}

.linkButton {
margin-top: 6px;
background: none;
border: none;
color: #60a5fa;
color: var(--seamless-accent-soft, #60a5fa);
font-size: 0.8rem;
cursor: pointer;
}
Expand Down
Loading
Loading