Kathuko › Oracle APEX › Part 9

Part 9

Give Turnout a brand

In this part Turnout stops looking like a stock APEX app and starts looking like a product: its own logo, colors, type and tone, applied through Universal Theme rather than fought against it.

People judge an app in the first few seconds, before they read a word. A club organizer deciding whether to trust Turnout with their members' emails will look at the header, the buttons and the cards. A stock theme says "internal tool". A considered brand says "someone cares about this".

The brand is modeled on graycells.us.com: calm warm neutrals, one confident accent color, generous spacing and plain, friendly words. You will learn the pattern, not the colors, so you can brand any APEX app the same way.

What you build:

  • A brand sheet: Turnout's colors, type and logo written as design tokens
  • One stylesheet that sets Universal Theme's CSS variables from those tokens
  • An SVG logo in the header and an app icon
  • Why three settings you already chose in Part 4 matter (read-only fields, hot buttons, buttons in the region header), and the one fix still to make: the empty icon tile on every hero

The rule for the whole part: change Universal Theme through its variables and template options, never by overriding its class names. Variables survive upgrades; class overrides break on the next APEX release.

Before you start

You need Turnout as it was at the end of Part 8. If you don't have it, import the Start here app, turnout-app-part-8.zip, from the downloads at the end of this page.

Download the two brand files now; you'll upload both to the app:

No SQL in this part. Everything happens in Shared Components and in the page properties.

Lesson 9.1 The brand on paper

Before touching APEX, write the brand down as design tokens: named values for every color and size the app uses. Tokens turn "make it look nicer" into decisions you can review, and they become the first block of the stylesheet in Lesson 9.4.

Turnout borrows the graycells.us.com palette (near-white bands, near-black text, a blue link) and adds one accent of its own: ember, a warm orange that says "come along" without shouting.

Token Value Used for Why this value
--tn-ink #1d1d1f Text Softer than pure black, easier on the eye over long pages
--tn-muted #6e6e73 Secondary text: club names, venues Still passes contrast on white and on the page band
--tn-paper #ffffff Cards and regions Content sits on white tiles
--tn-band #f5f5f7 Page background The tiles stand out without borders
--tn-rule #d2d2d7 Hairlines Separates the header without a heavy line
--tn-link #0066cc Links, focus rings People already read this blue as "clickable"
--tn-ember #c2410c Hot buttons, logo, current tab The one accent. Dark enough for white text on it
--tn-ember-dark #9a3412 Hover, current tab text A darker step of the same hue
--tn-ember-tint #fff1e8 Soft highlight behind the current tab A light step of the same hue
--tn-radius 18px Cards and regions Friendly, matches the rounded tiles of the Graycells site
--tn-font System font stack All text Loads instantly, looks native on every device, nothing to license

Three rules keep a brand from drifting:

  1. One accent. Ember marks the thing to do next: the hot button, the current tab. If everything is orange, nothing is.
  2. Neutrals do the work. Most of the screen is ink on white on light gray. That is what makes the accent visible.
  3. Words are part of the brand. Turnout says "Sign in to RSVP", not "Authenticate". Plain, short, friendly: the same voice as the Graycells site.

Try it: pick a different accent for your own copy of Turnout. Check it against white text in Lesson 9.6 before you commit to it.

Lesson 9.2 How Universal Theme takes a brand

Universal Theme is built in layers, and a brand belongs in the top one: your CSS variables, loaded last, so they win without fighting anything.

  1. The theme (Universal Theme, theme 42) provides every template: pages, regions, cards, buttons.
  2. The theme style (Turnout uses Iris) sets a palette by assigning values to CSS variables such as --ut-palette-primary and --ut-header-background-color.
  3. Your stylesheet assigns new values to the same variables. APEX loads application CSS after the theme style, so your values replace Iris's.

You have three ways to change those variables:

Way Where Good for Watch out
Theme Roller Developer toolbar › Customize Trying colors live, learning which variable does what Saves a new theme style inside the app, hard to review in Git
Application CSS file Shared Components › Static Application Files, linked in User Interface › CSS A brand you keep, review and version You need the variable names (Lesson 9.4 lists them)
Inline page CSS Page › CSS › Inline One page that truly differs Scatters the brand across pages. Avoid

Turnout uses the CSS file, because the brand should live in one file you can read, diff and reuse in the next app.

Why variables, not classes. You could write .t-Header { background: white }. It works today. But class names and their structure change between APEX releases, and your rule then silently stops matching, or matches something new. Variables are the theme's public interface: Oracle keeps their names stable precisely so you can brand apps safely.

How to find a variable's name: open the app, right-click the element, choose Inspect, and look in the Styles panel for var(--…). That is how the header menu bar in Lesson 9.4 was found: it is colored by --ut-header-menubar-background-color, not by the --ut-navtabs-* variables you might guess.

Try it: open Theme Roller on any page, change the primary color, and watch which parts follow. Close it without saving.

Turnout's mark is three people turning out together, white on an ember tile. It is a 1 KB SVG: sharp at any size, recolorable in a text editor, and no image tools needed.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Turnout">
  <rect width="64" height="64" rx="15" fill="#c2410c"/>
  <g fill="#fff">
    <circle cx="18" cy="26" r="5"/>
    <path d="M9 47a9 9 0 0 1 18 0z"/>
    <circle cx="46" cy="26" r="5"/>
    <path d="M37 47a9 9 0 0 1 18 0z"/>
  </g>
  <circle cx="32" cy="21" r="6.5" fill="#fff" stroke="#c2410c" stroke-width="2.5"/>
  <path d="M20 50a12 12 0 0 1 24 0z" fill="#fff" stroke="#c2410c" stroke-width="2.5"/>
</svg>

The middle person has an ember outline, which cuts a thin gap between them and the two beside them. That gap is what makes three shapes read as three people.

A lesson from the first draft. The first version had three dots over a curved line. At header size it looked like a frowning face. Always look at a logo at the size people will see it, 28 pixels high, not at the size you drew it.

Put it in the app

  1. Shared Components › Static Application Files › Create File. Type img in Directory, choose turnout-mark.svg as the file, and click Create. APEX serves it as #APP_FILES#img/turnout-mark.svg.
  2. Shared Components › User Interface Attributes, the Logo section. Set Logo Type to Image and Text, Image URL to #APP_FILES#img/turnout-mark.svg, Text to Turnout. Apply Changes.

Check: run any page. The header shows the mark beside the word Turnout.

In the APEXlang source the same thing is two small pieces. The file is listed in shared-components/static-files.apx and stored next to it in static-files/img/. The logo lives in application.apx:

logo {
    type: imageAndText
    imageUrl: #APP_FILES#img/turnout-mark.svg
    text: Turnout
}

Why #APP_FILES# and not a web address: the file travels with the app when you export it, it is cached by the browser, and the app never depends on another site being up.

Lesson 9.4 One brand stylesheet

The whole brand is one file, css/turnout.css, in three blocks: the tokens from Lesson 9.1, the tokens mapped onto Universal Theme's variables, and one block for hot buttons.

Block 2: map the tokens onto the theme

This is an excerpt to read; the download has every line, including the header, hero and sign-in variables.

Each line hands one token to one Universal Theme variable. Read it as "the theme's primary color is our ember".

:root {
  --a-base-font-family: var(--tn-font);

  --ut-palette-primary:          var(--tn-ember);
  --ut-palette-primary-contrast: #ffffff;
  --ut-palette-primary-shade:    var(--tn-ember-tint);
  --ut-link-text-color:          var(--tn-link);
  --ut-focus-outline-color:      var(--tn-link);

  --ut-body-background-color:       var(--tn-band);
  --ut-body-text-color:             var(--tn-ink);
  --ut-body-title-background-color: var(--tn-band);

  --ut-header-background-color: var(--tn-header);
  --ut-header-text-color:       var(--tn-ink);
  --ut-header-border-color:     var(--tn-rule);
  --ut-header-menubar-background-color:              var(--tn-header);
  --ut-header-menubar-item-text-color:               var(--tn-ink);
  --ut-header-menubar-item-border-color:             transparent;
  --ut-header-menubar-item-hover-background-color:   rgba(0, 0, 0, 0.04);
  --ut-header-menubar-item-current-background-color: var(--tn-ember-tint);
  --ut-header-menubar-item-current-text-color:       var(--tn-ember-dark);

  --ut-component-background-color: var(--tn-paper);
  --ut-component-border-color:     rgba(0, 0, 0, 0.06);
  --ut-component-border-radius:    var(--tn-radius);
  --ut-region-border-radius:       var(--tn-radius);
  --a-cv-border-radius:            var(--tn-radius);
  --a-cv-border-color:             rgba(0, 0, 0, 0.06);
  --a-cv-subtitle-text-color:      var(--tn-muted);
  --a-cv-badge-border-radius:      999px;

  --a-button-border-radius: 999px;
  --a-button-font-size:     .875rem;
}

The prefixes tell you who owns a variable: --ut- belongs to Universal Theme (page, header, regions), --a- to APEX's shared components (buttons, cards, fields), and --tn- is ours.

Block 3: hot buttons

After Block 2 the hot buttons were still near-black. Inspecting one showed why: Iris sets --a-button-background-color: #161513 directly on the .t-Button--hot class, so the primary palette never reaches it. The fix still only sets variables, on the same class Iris uses:

.t-Button--hot,
.a-Button--hot {
  --a-button-background-color:        var(--tn-ember);
  --a-button-text-color:              #ffffff;
  --a-button-hover-background-color:  var(--tn-ember-dark);
  --a-button-active-background-color: #7c2d12;
}

This is the one place we name a class, and we use it only to set the button's own variables, never background: directly.

Wire it in

  1. Static Application Files › Create File, Directory css, file turnout.css. The download is the finished file: the tokens from Lesson 9.1, Block 2 and Block 3, plus the sign-in page variables from Lesson 9.5.
  2. Shared Components › User Interface Attributes, the CSS section, File URLs: #APP_FILES#css/turnout.css. Apply Changes.

Check: reload the app. The page is light gray, cards are white with rounded corners, and hot buttons are ember.

In APEXlang this is a css group in application.apx:

css {
    fileUrls: #APP_FILES#css/turnout.css
}

Try it: change --tn-radius to 4px and reload. Every card and region squares off at once. That is the payoff of tokens.

Lesson 9.5 Fixing what looked off

Three things in the first build of Turnout looked unfinished. None needed CSS: each was a template or a setting.

If you built pages 10 and 20 with the Build it steps in Part 4, you already chose the second and third fixes: the display items use Optional - Above, and Sign in to join is hot. The table explains why those were the right choices. The icon tile is the one left to fix.

What looked off Cause Fix
An empty colored square beside "Find your people" on Discover The Hero region template shows an icon tile, and none was set Region › Image › File URL: #APP_FILES#img/turnout-mark.svg. The hero now shows the Turnout mark
Every field on the Event and Club pages sat in a dashed box Display Only items used the Optional - Floating label template, which outlines the value as if it were an input Item › Appearance › Template: Optional - Above. The label sits above, the value reads as plain text
"Sign in to join" was grey, "Sign in to RSVP" was the main color Only one of the two buttons was marked Hot Button › Appearance › Hot: On, for the one action you want people to take on each page

Why floating labels are wrong here. Floating labels are made for inputs: the label rests inside the empty box and floats up when you type. A read-only value has nothing to type, so the box just looks like a disabled form. Use Above for anything people read rather than edit.

One hot button per page. Hot means "this is what you came here to do". On the Event page that is RSVP; on the Club page it is Join. Two hot buttons on one page and the visitor has to think.

In APEXlang the fixes are three short edits:

// p00001-discover.apx, in the hero region
image {
    fileUrl: #APP_FILES#img/turnout-mark.svg
}

// p00010-club.apx and p00020-event.apx, every display item
appearance {
    template: @/optional-above
}

// p00010-club.apx, button sign-in-to-join
appearance {
    buttonTemplate: @/text
    hot: true
}

Found after the first import

Clicking through the imported app turned up three more. This is normal: a brand only shows its gaps on every page, not the one you styled.

What looked off Cause Fix
Sign-in page showed an empty ember tile, on a gray-on-gray card The Login region template has the same icon tile as the hero; Iris gives the sign-in page its own background variables Region › Image: the mark. In the stylesheet: --ut-login-page-background-color, --ut-login-region-background-color, --ut-login-region-filter, --ut-login-region-box-shadow
Clubs opened with a plain title bar, Discover with a hero Two pages, two templates Clubs gets the same Hero region with the mark. Every top-level page now opens the same way
"Sign in to RSVP" floated alone in an empty strip above the details The buttons used the Next position, which the Standard region draws as a row of its own at the top of the body Button › Layout › Position: Edit. It sits in the region header, beside the title

Two copies of one app. If an import creates a new app ID instead of replacing the old one, both apps share the alias, and the friendly URL keeps opening the old one. Delete the old app, or import over its ID.

Build it.

  1. The hero images. These pages open with a Hero region: Discover (1), My events (30), My clubs (32), the organizer pages 100, 110, 130 and 140, and Club approvals (200). On each, click the hero region in the Breadcrumb Bar and set Image › File URL to #APP_FILES#img/turnout-mark.svg. Eight pages, one property each.
  2. A hero for Clubs. Page 2 has no hero yet. Right-click Body, Create Region, and set Layout › Slot to Breadcrumb Bar: Name and Title Clubs, Type Static Content, Source › HTML Code Every club on Turnout. Open one to see what's coming up, then join., Appearance › Template Hero, Image › File URL #APP_FILES#img/turnout-mark.svg.
  3. The sign-in page. On page 9999, click the Login region (template Login) and set Image › File URL to the same mark. The sign-in background variables are already in turnout.css.

Check: sign out and back in. The sign-in card shows the mark on a light page, and every top-level page opens with the mark beside its title.

Lesson 9.6 Checking the brand

A brand is finished when it passes three checks: every text color is readable, it works at phone width, and it looks right in the real app, not just in your head.

Contrast

WCAG AA asks for a contrast ratio of at least 4.5 to 1 for normal text. Every pair Turnout uses passes:

Text On Ratio AA (4.5)
Ink #1d1d1f Band #f5f5f7 15.5 Pass
White Ember-dark #9a3412 (button hover) 7.3 Pass
Ember-dark #9a3412 Ember-tint #fff1e8 (current tab) 6.6 Pass
Link #0066cc Paper #ffffff 5.6 Pass
White Ember #c2410c (hot button) 5.2 Pass
Muted #6e6e73 Paper #ffffff 5.1 Pass
Muted #6e6e73 Band #f5f5f7 4.7 Pass

This is why the accent is #c2410c and not a brighter orange: a bright orange such as #f97316 with white text drops to about 2.8 and fails. Check any accent you choose with a contrast checker before you build on it.

Phone width

Open the app in your browser's device toolbar at 390 pixels wide. Check that the header shows the mark and name on one line, the menu bar scrolls rather than wrapping, the cards stack in one column, and buttons are at least 40 pixels tall. Universal Theme handles the layout; your job is to confirm your tokens didn't break it.

Preview before you import

You can try a stylesheet on the live app before importing anything. Open any Turnout page, open the browser console, and paste:

const s = document.createElement('style');
s.textContent = `/* paste turnout.css here */`;
document.head.appendChild(s);

The page restyles at once and a reload undoes it. This is how the frowning logo and the dark menu bar were caught before they reached the app.

Try it (stretch): Turnout v1 is light only. Add a dark version as a second token block inside @media (prefers-color-scheme: dark) that redefines only the --tn- tokens. If Block 2 is written well, you never touch the mapping.

Checkpoint

After this part Turnout has a light header with its mark, an ember highlight on the current tab, ember hot buttons, white rounded cards on a light gray page, and clean read-only fields. The app validates with no errors or warnings.

File Change
shared-components/static-files/css/turnout.css New: tokens, theme mapping, hot buttons
shared-components/static-files/img/turnout-mark.svg New: the Turnout mark
shared-components/static-files.apx New: lists the two files
application.apx Logo set to image and text; brand stylesheet linked
pages/p00001-discover.apx and the other hero pages Hero shows the mark

After the first import: p00002-clubs.apx (hero instead of title bar), p09999-login.apx (mark on the sign-in page), and the sign-in variables in turnout.css.

Exercises

  • Give the Event page a hero with the event title and club name, the same way Discover has one.
  • Add app icons for phones: export the mark as 192 and 512 pixel PNGs and add them under Progressive Web App.
  • Rebrand your copy with your own accent color, then run the contrast table in Lesson 9.6 against it.
  • Stretch: the dark token block from Lesson 9.6.

Downloads for Part 9

Everything this part uses. Each file is plain text you can open in any editor before you run it.

Part 9 quiz

5 questions. Get 4 right to pass. You can retake it as often as you like.