Following the design system

An app screen runs inside an iframe, so the Logpresso Sonar web console stylesheet does not apply to it. The isolation keeps the app's styles from breaking the console, but in exchange the app has to rebuild the design specifications itself. That is where most of the difference between an app screen and the rest of the product comes from.

The source for color values

The source for token values is the stylesheet the design system publishes.

https://design.logpresso.com/sonar5.css

That file defines every --color- variable the Logpresso Sonar web console uses, and an app takes those values as they are. When you need to confirm a color, query this file rather than eyeballing the screen.

The other material the design system publishes serves a different role, so reference them separately.

ReferenceWhat you get
sonar5.cssThe actual color token values
Component documentsComponent states, sizes, and slot specifications
Screen pattern documentsHow screens are composed
Token definitionsTypography and spacing specifications, token classification

design-tokens.json uses names like --accent and --text, different from sonar5.css. That file is the design system site's own token registry and does not map one-to-one onto the variable names the product uses. Take the color variables you apply in an app from sonar5.css, and use this file to confirm typography and spacing specifications and how tokens are classified.

Developing with an AI agent covers the order to read this material in when you build a new screen.

Defining tokens

Tailwind CSS 4 generates utility classes from the variables you define in the theme block of your stylesheet. Define the tokens once and you can use classes like bg-surface and text-base-content-100 directly.

This is src/main/ui/src/styles/global.css. You do not have to define the tokens again for each app. Take this file as it is when you start a new app and add only the values that app needs.

@import "tailwindcss";

@theme {
  /* brand / accent (theme-independent) */
  --color-primary: #ff692a;
  --color-primary-100: #ff7d4a;
  --color-primary-200: #f46226;
  --color-primary-300: #e65b22;
  --color-primary-alpha: rgba(255, 105, 42, 0.5);
  --color-primary-alpha-100: rgba(255, 105, 42, 0.15);
  --color-primary-alpha-200: rgba(255, 105, 42, 0.3);

  /* surfaces (light) */
  --color-surface: #f3f5fa;
  --color-neutral: #ffffff;
  --color-neutral-100: #f4f8fd;
  --color-neutral-200: #eaf1fa;
  --color-neutral-300: #d9e3f4;
  --color-neutral-400: #becbe7;
  --color-neutral-500: #9eb0d4;
  --color-neutral-600: #889cc8;
  --color-neutral-700: #7388bb;
  --color-neutral-900: #405691;
  --color-neutral-alpha-100: rgba(23, 39, 101, 0.04);
  --color-neutral-alpha-200: rgba(23, 39, 101, 0.08);
  --color-neutral-alpha-300: rgba(23, 39, 101, 0.16);
  --color-neutral-alpha-400: rgba(23, 39, 101, 0.24);
  --color-neutral-alpha-500: rgba(23, 39, 101, 0.35);

  /* text (light) */
  --color-base-content: #191919;
  --color-base-content-100: #4d4d4d;
  --color-base-content-200: #808080;
  --color-base-content-300: #d0d0d0;
  --color-base-content-400: #ebebeb;

  /* status (theme-independent) */
  --color-info: #e3f2ff;
  --color-info-100: #36a4ff;
  --color-info-200: #0875dc;
  --color-success: #e4f8ee;
  --color-success-100: #00cc88;
  --color-success-200: #009f59;
  --color-warning: #fff8e4;
  --color-warning-100: #ffca48;
  --color-warning-200: #fba434;
  --color-error: #ffebef;
  --color-error-100: #ff454d;
  --color-error-200: #e4062c;
  --color-system: #f2e9ff;
  --color-system-100: #915cff;
}

/* Dark theme — overrides surfaces, text and alpha only (brand/status are shared). */
[data-theme='dark'] {
  --color-surface: #0b0f15;
  --color-neutral: #070b13;
  --color-neutral-100: #0e1322;
  --color-neutral-200: #151c33;
  --color-neutral-300: #1d2544;
  --color-neutral-400: #262f56;
  --color-neutral-500: #303a6a;
  --color-neutral-600: #3b4680;
  --color-neutral-700: #465294;
  --color-neutral-900: #616ebb;
  --color-neutral-alpha-100: rgba(126, 140, 222, 0.04);
  --color-neutral-alpha-200: rgba(126, 140, 222, 0.08);
  --color-neutral-alpha-300: rgba(126, 140, 222, 0.16);
  --color-neutral-alpha-400: rgba(126, 140, 222, 0.24);
  --color-neutral-alpha-500: rgba(126, 140, 222, 0.35);

  --color-base-content: #ebebeb;
  --color-base-content-100: #b5b5b5;
  --color-base-content-200: #808080;
  --color-base-content-300: #333333;
  --color-base-content-400: #191919;
}

The tokens fall into four families.

FamilyPrefixPurpose
Brand--color-primaryEmphasis, selection, focus
Surface--color-surface, --color-neutral*Page background, cards, dividers
Text--color-base-content*Body text, secondary text, disabled text
Status--color-info/success/warning/error/systemInformation, success, warning, error, system

As the number rises, surface colors get darker and text colors get lighter. Tokens with alpha in the name are translucent colors meant to layer over a background.

Only the page background token has a different name

The app's token names match sonar5.css. There is exactly one exception: the page background.

Source (sonar5.css)App stylesheetValue (light theme)
--color-base--color-surface#f3f5fa

The rename exists because of a collision with Tailwind. Define a token named --color-base and Tailwind generates text-base as a color utility. But text-base is already a font-size utility in Tailwind. The color utility shadows it, so body text wherever you set the size with text-base gets painted in the page background color. Text the same color as the background is invisible, so part of the screen appears empty.

Use the value of --color-base from sonar5.css as-is and name it --color-surface.

Dark theme

The dark theme redefines only surfaces, text, and translucent colors. Brand and status colors are shared between the two themes. The dark theme block in sonar5.css does not redefine --color-primary either.

Because the variable names are the same, overriding just the values under [data-theme='dark'] recolors every utility class at once. Screen code needs no theme branching.

Creating a UI project explains how to follow the web console theme, under theme integration.

Control specifications

Buttons, inputs, and select boxes come in two sizes.

SizeHeightRadiusHorizontal paddingTextWhere
Small24px8px8px12px MediumThe default for operational screens
Medium30px8px12px14pxDialog confirm and cancel

Use Small on operational screens that work with lists and tables. They display a lot of information, so reducing the space controls take up is the product's default direction.

Borders are 1px. Icons render at 16px.

The sample app defines the specification as a class.

@layer components {
  .ctl-sm {
    height: 24px;
    padding: 0 8px;
    border-radius: 8px;
    border: 1px solid var(--color-neutral-300);
    background: var(--color-neutral);
    color: var(--color-base-content);
    font-size: 12px;
    font-weight: 500;
    line-height: 22px;
  }
}

Always define a class like this inside @layer components. Unlayered CSS has higher priority than CSS inside a layer. Define the class outside a layer and you cannot adjust individual properties with Tailwind utilities.

The search input is an example. Putting an icon on the left means widening the input's left padding.

<input type="text" className="ctl-sm pl-7" />

Define .ctl-sm outside a layer and its padding: 0 8px overrides pl-7, the padding does not widen, and the icon overlaps the text.

The layer order in Tailwind CSS 4 is theme, base, components, utilities. Put shared classes in components and utilities win, which lets the caller adjust the properties it needs.

Table specifications

Tables on list screens follow these rules.

  • Rows carry no background color. Do not alternate the background of even and odd rows.
  • Only the selected row is tinted. Use --color-primary-alpha-100 as its background.
  • A 1px rule sits under the header. It uses --color-primary. This rule is the signature of a product table.
<tr className="border-b border-primary">
  <Th>Name</Th>
  ...
</tr>

Align numeric columns to the right and use tabular figures so the digits do not shift.

Expressing state

Handle all six states. Default, loading, empty, error, permission, and selected. Building only the screen with data in it is the most common omission.

Word the empty state according to its cause. Getting no search results and having no data at all lead the user to different next actions.

No subnet groups match this search. Clear the keyword or try another one.
This profile has no subnet groups.

The first sentence tells the user to clear the keyword; the second tells them data has to be registered. Show only "No data" for both and the user cannot tell what to do.

Offer a way to retry in the error state. Display only the error message and the user has no option but to reload the screen.

Do not convey information through color alone. State, severity, and selection have to be distinguishable by text, an icon, or an accessibility attribute as well. Setting aria-selected alongside the background color on a selected table row is one example.

Do not remove the keyboard focus indicator.

:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

Remove the focus indicator and users working from the keyboard cannot tell where they are. Do not drop it because the outline looks unattractive.

Typography

Use Pretendard for text and D2 Coding for code and queries.

PurposeSizeLine height
caption12px18px
body14px20px
body-lg16px24px
title18px28px

Use the Regular, Medium, and Bold weights.

body {
  font-family:
    'Pretendard',
    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Hiragino Kaku Gothic ProN', 'Hiragino Sans',
    'Yu Gothic UI', 'Yu Gothic', 'Meiryo',
    sans-serif;
  font-size: 14px;
  line-height: 20px;
}

pre, code {
  font-family: 'D2 Coding', 'Consolas', monospace;
}

The Japanese fonts are in the fallback list to keep characters from breaking in a Japanese environment.

Detail panels

Selecting an item in a list often shows its details in a panel on the right. Size that panel at 60% of the screen with a minimum of 720px, which keeps the contents from being squeezed on a narrow screen.

When you make the panel closable with the ESC key, register the event in the capture phase. Otherwise an input element inside the panel handles the key first and the panel does not close.

Agent prompt

Use this to review whether a screen follows the specifications.

Review whether a Logpresso app screen follows the design system.

Target: (file paths)

Follow the reading order:
1. https://design.logpresso.com/design-system.manifest.json
2. https://design.logpresso.com/docs/AI-AGENT-GUIDE.md
3. the pattern document for this screen
4. the documents for the components used
5. the foundation documents you need

Check color values against https://design.logpresso.com/sonar5.css.

Check:
- tokens: values match sonar5.css, the page background is defined as --color-surface
- controls: the operational default is height 24 / radius 8 / 12px Medium
- icons: 16px
- tables: no row background, only the selected row in primary-alpha-100, a 1px primary rule under the header
- states: all six of default, loading, empty, error, permission, selected are implemented
- empty states: no search results and no data are worded differently
- accessibility: state is not conveyed by color alone, the focus indicator is intact
- dark theme: only surfaces and text are overridden under [data-theme='dark']

If you cannot find a token value or component specification in the documents, do
not invent it — report that it needs confirming. Cite the document backing each
problem you report.

That covers everything you need for XDR UI development. The next section summarizes the app APIs used so far.