Credential Expiry Warning Card — Color-Escalating API Key / Certificate Countdown

Credential Expiry Warning Card — Color-Escalating Countdown · Cards · Plain HTML, CSS & JS · Live preview

CategoryCards

What's included

Features

Single ordered tier-classification function drives every visual signal — border color, icon, message tone, and button label — for a given credential
Most-urgent-first threshold checking correctly prevents an already-expired credential from being mis-classified into a less urgent tier
Human-readable expiry wording (including a correct negative-days "expired X days ago" case) is a separate concern from tier classification
Threshold values (what counts as critical vs warning) live in exactly one place, with every visual consequence following automatically from a single change
Action button label itself escalates with severity ("Manage" through "Rotate now"), reinforcing urgency beyond just color
Generic rendering from a plain data array — adding new credentials requires no new classification or styling logic
Singular/plural day-count wording handled correctly ("Expires tomorrow" vs "Expires in 9 days" vs "Expired 1 day ago")

About this UI Snippet

Credential Expiry Warning Cards — One Classification, Every Visual Signal

Screenshot of the Credential Expiry Warning Card — Color-Escalating Countdown snippet rendered live

Expired API keys, lapsed SSL certificates, and stale service tokens cause real production incidents, and they usually fail silently until the moment something breaks. A warning card that surfaces "how much time is left" clearly — and escalates its visual urgency correctly as that time shrinks — is a small UI investment against a genuinely costly failure mode. The key implementation detail: every visual signal for a given credential must be derived from the *same* classification, or the card risks showing conflicting severity cues.

One ordered tier list, checked most-urgent-first

classify() checks a credential's daysRemaining against thresholds in a specific order — expired (≤0 days) first, then critical (≤3 days), then warning (≤14 days), falling through to a calm default otherwise. This ordering isn't arbitrary: if the checks ran in the opposite order (warning first), a credential that's actually *already expired* (a negative number) would incorrectly satisfy the "≤14 days" warning check before ever reaching the "≤0" expired check, since a negative number is also less than 14. Checking most-urgent-first and returning immediately on the first match is what guarantees a genuinely overdue credential can never be mis-classified as merely "warning" tier.

Every visual element reads from the same tier object

The returned tier object carries a CSS class name, an icon, and an action-button label all together, as one unit — the card's border color, background tint, icon, and button text are all derived from this single object in one render pass, rather than four separate conditional checks scattered through the markup that could each independently drift out of sync with the others. If a card is styled with the "critical" red border, its icon, message tone, and button label are *guaranteed* to also reflect critical — there's no code path where one signal updates without the others.

Human-readable detail text is a separate concern from tier classification

formatDetail() handles the *wording* ("Expires in 9 days" vs "Expired 3 days ago" vs "Expires tomorrow") entirely independently of classify(), which only handles *which tier applies*. Keeping these separate means the exact day-count thresholds for changing color can be tuned without touching the wording logic, and the wording logic (singular vs plural "days", the "tomorrow" special case) can be refined without touching threshold values — two genuinely distinct concerns, kept as two distinct functions.

Threshold values live in exactly one place

The specific cutoffs (0, 3, 14 days) appear in classify()'s conditionals and nowhere else — there's no duplicated "is this expiring soon" check anywhere else in the rendering logic that could independently drift to a different threshold over time. Changing what counts as "critical" requires editing one number in one function, with every visual consequence of that change following automatically.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain precisely why the tier-classification checks must run in most-urgent-first order, walking through a concrete example of what would go wrong with a negative daysRemaining value if the order were reversed. It's also worth asking for a version that also computes and displays a specific calendar expiry date (not just a relative day count), or one that groups credentials by tier with a collapsible section per severity level so critical items are impossible to miss even in a long list.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a credential expiry warning card list in HTML, CSS, and vanilla JavaScript — no external library.

Requirements:
- A list of at least four named credentials (e.g. API keys, a certificate, an access token), each with a numeric "days remaining until expiry" value — including at least one credential that has ALREADY expired (a negative days-remaining value).
- Implement a single classification function that maps a days-remaining number to an urgency tier (e.g. calm/default, warning, critical, expired), checking thresholds in order from MOST urgent to LEAST urgent and returning on the first match — explain in a code comment why this specific ordering is required to correctly handle an already-expired (negative) value.
- The classification function's single result object must supply everything needed to render that credential's card — a CSS class controlling border/background color, an icon, and an action button label — so that a card's color, icon, and button label can never independently disagree about which urgency tier applies.
- Implement a separate function purely for generating human-readable expiry text (e.g. "Expires in 9 days", "Expires tomorrow", "Expired 3 days ago" with correct singular/plural wording) — keep this wording logic entirely separate from the tier-classification logic.
- Render the full list generically from a plain data array of credentials, so adding a new credential requires no additional classification or styling code.
- Clicking a credential's action button should show a visible confirmation state (disabling the button and updating its label), standing in for where a real credential-rotation request would be triggered.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="demo" id="demo"></div>

Step by step

How to Use

  1. 1
    Review the credential listEach row shows a name, a human-readable expiry detail, and a color-coded card matching its urgency tier — calm, warning (amber), critical (red), or expired (dark red).
  2. 2
    Notice the already-expired entryThe S3 access key shows a dark "expired" state with a negative-day count correctly converted into "Expired 3 days ago" rather than a confusing negative number.
  3. 3
    Click "Rotate now" or "Rotate soon"Confirms the action in this demo — in a real implementation this would trigger an actual credential rotation flow.
  4. 4
    Adjust the CREDENTIALS arrayAdd your own credentials with a name and daysRemaining value — classify() and formatDetail() handle rendering generically.
  5. 5
    Tune the tier thresholdsChange the day-count cutoffs inside classify() (currently 0, 3, and 14) to match your own organization's rotation policy.

Real-world uses

Common Use Cases

DEVOPS
API key and secret rotation dashboards
Internal tools tracking API keys, service tokens, and secrets that need periodic manual or automated rotation before expiry.
SECURITY
SSL/TLS certificate expiry tracking
Certificate management tools where a lapsed cert can cause a real production outage, needing clear escalating urgency well before expiry.
ADMIN
Admin panel security/compliance widgets
Compliance-focused admin dashboards surfacing any credential, permission grant, or access token nearing its expiry.
DEVTOOLS
Developer portal credential management
Self-service developer portals where users manage their own API keys and need clear, unmistakable expiry warnings.
Related: Image Hover Reveal Cards
See the Image Hover Reveal Cards for a related cards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Because a negative daysRemaining value (already expired) also technically satisfies a "less than or equal to 3" critical check. Checking the most urgent tier (expired) FIRST and returning immediately on a match guarantees an overdue credential is correctly classified as expired, not accidentally caught by a less urgent, more loosely matching threshold checked earlier.

No — both are read from the exact same tier object returned by a single call to classify(), applied together in one render pass. There is no separate, independently-maintained conditional for the icon versus the border color that could drift out of sync with each other.

formatDetail() explicitly checks for daysRemaining <= 0 and formats it as "Expired X days ago" (using the absolute value and correct singular/plural wording), rather than displaying a confusing raw negative number like "Expires in -3 days."

Edit the threshold numbers directly inside classify() — currently 3 days for critical and 14 days for warning. Because every visual signal is derived from this one function's output, changing these numbers automatically and correctly updates the color, icon, and button label behavior for every credential without touching any other code.

No — formatDetail() and classify() are deliberately separate functions. classify() only determines which urgency tier applies; formatDetail() only determines how to word the specific day count. This separation lets you refine either concern independently without affecting the other.

In this demo, it disables itself and shows a confirmation label — in a real implementation, you'd replace that handler with an actual credential rotation request, likely followed by re-fetching or updating the credential's daysRemaining value once rotation completes.