The widget’s appearance is driven by a small set of tokens you set in the dashboard’s Customize section. The same tokens can also be passed inline via the theme attribute on the <queueup-form> element. For deeper control, override CSS variables or target the widget’s ::part hooks from your stylesheet.
Live editor
Open the dashboard and navigate to the waitlist’s Setup → Customize section. The page shows a live preview of the widget rendered against your current settings; every change updates instantly. When you’re happy, click Save to persist.
Tokens
| Token | Type | Default | Description |
|---|---|---|---|
brand_color | hex #RRGGBB | #f97316 | Button background and accents. |
text_color | hex #RRGGBB | #000000 | Text colour for input and button label. |
border_radius | integer (0 to 32) | 6 | Corner radius for input and button, in pixels. |
layout | "stacked" or "inline" | "stacked" | Vertical (input above button) or horizontal (input + button on one row). |
button_label | string (1 to 32) | "Join waitlist" | Button text. |
placeholder | string (0 to 64) | "[email protected]" | Email input placeholder. |
Inline theme
The theme attribute on <queueup-form> carries the JSON object you copied from the dashboard. The widget reads it on mount and renders against those values:
<queueup-form
waitlist-id="wl_abc123"
theme='{"brand_color":"#10b981","layout":"inline","button_label":"Get notified"}'
></queueup-form>
You don’t need to specify every token. Anything you omit falls back to the system default. Drop the attribute (or pass theme="none") to render with system defaults entirely.
Custom styling
Theme tokens cover the common dials. For full control (custom borders, gradients, font stack, hover states), the widget exposes CSS hooks you can target from your page’s stylesheet.
::part hooks
The widget renders inside a Shadow DOM, so your page’s CSS can’t reach into it directly. Every meaningful element carries a part attribute. Target those with the ::part() selector:
queueup-form::part(input) {
border: 2px solid #111;
font-family: "Inter", sans-serif;
}
queueup-form::part(input):focus {
border-color: #f97316;
box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.2);
}
queueup-form::part(button) {
background: linear-gradient(135deg, #f97316, #ef4444);
font-weight: 600;
letter-spacing: 0.02em;
}
queueup-form::part(button):hover {
filter: brightness(1.05);
}
queueup-form::part(message-error) {
color: #dc2626;
}
queueup-form::part(message-success) {
color: #16a34a;
}
queueup-form::part(brand) {
display: none;
}
Available parts
| Part | What it targets |
|---|---|
form | The <form> element wrapping the input and button. |
form-success | Added to form after a successful submission. |
field | A wrapper around the input. Useful for adornments. |
input | The email input. |
button | The submit button. |
button-success | The success label inside the button (the “You’re on the list” state). |
footer | The row containing the message slot and brand. |
message | The message slot (empty by default). |
message-error | Added to message when an error is shown. |
message-success | Added to message when a success message is shown. |
brand | The “Powered by QueueUp” line. |
CSS variables
The widget reads its colours, radius, and spacing from CSS custom properties on the host element. You can override any of them:
queueup-form {
--qu-brand: #111111;
--qu-brand-fg: #ffffff;
--qu-text: #1f2937;
--qu-bg: #fafafa;
--qu-border: rgba(0, 0, 0, 0.15);
--qu-radius: 12px;
--qu-gap: 12px;
--qu-focus-ring: rgba(17, 17, 17, 0.25);
--qu-input-padding-y: 12px;
--qu-input-padding-x: 14px;
--qu-button-padding-y: 12px;
--qu-button-padding-x: 20px;
}
| Variable | Default | What it controls |
|---|---|---|
--qu-brand | from theme | Button background and focus accents. |
--qu-brand-fg | auto-contrast | Button text colour. |
--qu-text | from theme | Form text colour. |
--qu-bg | #ffffff | Input background. |
--qu-border | translucent slate | Input border. |
--qu-radius | from theme | Corner radius for input and button. |
--qu-gap | 8px | Gap between input and button. |
--qu-focus-ring | translucent blue | Focus outline ring. |
--qu-input-padding-y / -x | 10px / 12px | Input padding. |
--qu-button-padding-y / -x | 10px / 16px | Button padding. |
Fonts
The host element uses font: inherit, so the form picks up whatever font you set on <queueup-form> or any ancestor. There’s no separate font token; just style the host:
queueup-form {
font-family: "Inter", system-ui, sans-serif;
font-size: 15px;
}
Headless mode
If you want to start from a blank slate and style everything yourself, set mode="headless" on the element. The default stylesheet is skipped entirely; only the ::part hooks remain. You’re then responsible for every visual rule.
<queueup-form
waitlist-id="wl_abc123"
mode="headless"
></queueup-form>
In headless mode, the widget still renders the same DOM structure (form, field, input, button, footer, message, brand), so your existing ::part selectors keep working.
Events
The widget dispatches DOM events on success and failure so you can react to signups without owning the form markup.
| Event | When | detail shape |
|---|---|---|
queueup:ready | After mount, before submit. | { waitlistId } |
queueup:submit | When the user submits, before the network call. Cancellable. | { email } |
queueup:success | After a successful signup. | { email, statusToken, referralCode, statusUrl } |
queueup:error | On any error. | { code, message, retryable } |
A typical handler that surfaces the user’s referral link inline:
<queueup-form waitlist-id="wl_abc123"></queueup-form>
<div id="share" hidden>
Share to skip ahead: <code id="share-url"></code>
</div>
<script>
document.querySelector("queueup-form").addEventListener("queueup:success", (e) => {
const { statusUrl, referralCode } = e.detail;
if (!referralCode) return;
const share = document.getElementById("share-url");
share.textContent = `https://yoursite.example/?queueup_ref=${referralCode}`;
document.getElementById("share").hidden = false;
});
</script>
The statusToken and statusUrl fields are populated for both new signups and idempotent re-signups, so a returning user sees the same share UI as a first-time visitor. See Referrals for the full flow.
Snippet versioning
Every save in the dashboard bumps a version counter on the theme. The Embed section tracks which version you last copied; if the saved version is newer, you’ll see an Out of date banner suggesting you re-copy.
This matters because the embed inlines the theme JSON into the snippet, and the widget doesn’t fetch theme data at runtime. A change to the saved theme won’t appear on your site until you update the snippet on the page.