# default-ui.css

**A visual default layer for semantic HTML.**

One stylesheet. No classes, no JavaScript, no build step. You write plain semantic
HTML and it already looks like it came out of a design system.

```html
<link rel="stylesheet" href="css/default-ui.css">
```

```html
<h1>Tambah Buku</h1>

<form method="POST">
    <p>
        <label for="judul">
            Judul
            <input type="text" name="judul" id="judul" required>
        </label>
    </p>

    <button type="submit">Simpan</button>
    <a href="index.php">Batal</a>
</form>
```

That is the whole API.

---

## 1. Concept

Most CSS frameworks ask you to describe appearance in your markup:

```html
<button class="btn btn-primary">      <!-- Bootstrap  -->
<button class="px-5 py-3 bg-lime-400"> <!-- Tailwind   -->
```

`default-ui.css` reads appearance from the markup you already wrote:

```html
<button type="submit">
```

It is **not** a component framework, not a utility framework, and not just a reset.
It is the layer between the browser's User Agent stylesheet and your application:

```
Browser Default HTML
        ↓
   default-ui.css
        ↓
Custom Visual Language
```

---

## 2. Visual reference

The design language is taken from a single reference screenshot — a **neo-brutalist /
Y2K agency landing page**. Everything in the token layer was derived from it rather
than invented:

| Observed in the reference | Became |
|---|---|
| Pale yellow-green ground `#f5f8ce` | `--ui-color-background` |
| Lime CTA buttons and cards `#d6f03a` | `--ui-color-primary` |
| Bright green frames `#22c55e` | `--ui-color-accent` |
| Pink star / frame `#f08fdc` | `--ui-color-highlight` |
| Orange card outline `#f1541b` | `--ui-color-danger` |
| Black statistics bar | `--ui-color-inverse` |
| 2–3px solid black outline on every surface | `--ui-border` |
| Offset duplicate frames, no blur | `--ui-shadow-sm/md/lg` |
| Sharp corners; pill only on the active nav item | `--ui-radius-sm` / `--ui-radius-pill` |
| Extra-bold grotesk, tracked out, `line-height ≈ 0.95` | `--ui-text-4xl`, `--ui-tracking-display` |
| The word "Provide" inside a lime box | `mark`, `legend` |

Elements the reference did not show (tables, `details`, `meter`, …) were derived from
the parts it did show — the black statistics bar became `thead`, the lime cards became
`aside` and `tfoot` — rather than borrowed from another design system.

### Reference → semantic mapping

| Visual | Selector |
|---|---|
| Active nav pill | `nav a[aria-current]` |
| Lime CTA button | `button`, `[type="submit"]`, `a[role="button"]` |
| Highlight box | `mark`, `legend` |
| White card, black outline, offset shadow | `article`, `form`, `table`, `details`, `dialog` |
| Lime panel | `aside`, `blockquote`, `tfoot` |
| Black statistics bar | `body > footer`, `thead th`, `pre` |
| Thick photo frame | `figure > img`, `article > img` |

---

## 3. Philosophy

* **Semantic HTML is the API.** Element, attribute, and state selectors only.
* **No class API.** The stylesheet contains zero class selectors.
* **State comes from the platform.** `:hover`, `:focus-visible`, `:disabled`,
  `:user-invalid`, `[readonly]`, `[aria-current]`, `[aria-invalid]`, `[open]`.
* **The cascade does the work.** Three layers, zero class selectors, and `!important`
  only where the platform requires it — `[hidden]` and the reduced-motion override.
* **No dependencies.** No JavaScript, no npm, no runtime, no font requests.
  (`demo/index.html` opts into Archivo from Google Fonts to match the reference
  exactly — the library works without it.)

---

## 4. Install

### CDN (no download)

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/numen111104/nide-ui-default@v1.0.0/css/default-ui.min.css">
```

Pin a tag (`@v1.0.0`) for production. `@main` always tracks the latest commit:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/numen111104/nide-ui-default@main/css/default-ui.min.css">
```

### Self-hosted

Download `css/default-ui.css` (or `css/default-ui.min.css`) and link it:

```html
<link rel="stylesheet" href="/css/default-ui.min.css">
```

Nothing else. To view the demo locally:

```bash
python3 -m http.server 8777
```

Then open `http://localhost:8777/demo/index.html`. `demo/plain.html` is the same
markup **without** the stylesheet — open both side by side to see the difference.

Live demo: <https://numen111104.github.io/nide-ui-default/demo/>

---

## 5. Supported elements

**Document & landmarks**
`html` `body` `header` `nav` `main` `section` `article` `aside` `footer` `div`

**Typography**
`h1`–`h6` `p` `small` `strong` `b` `em` `i` `cite` `mark` `del` `ins` `abbr[title]`
`sub` `sup` `kbd` `code` `samp` `var` `pre` `blockquote` `hr`

**Lists**
`ul` `ol` `li` `::marker` `dl` `dt` `dd`

**Media**
`img` `svg` `video` `canvas` `iframe` `figure` `figcaption`

**Links**
`a` `a:hover` `a[target="_blank"]` `a[role="button"]` `nav a[aria-current]`

**Forms**
`form` `fieldset` `legend` `label` `input` `textarea` `select` `option` `output`
`::placeholder` `::file-selector-button`

Input types: `text` `number` `email` `password` `search` `url` `tel` `date` `time`
`datetime-local` `month` `week` `file` `color` `range` `checkbox` `radio`

**Buttons**
`button` `button[type="submit"]` `button[type="reset"]` `input[type="button"|"submit"|"reset"]`

**Tables**
`table` `caption` `thead` `tbody` `tfoot` `tr` `th` `td`

**Interactive**
`details` `summary` `dialog` `dialog::backdrop` `progress` `meter`

### States

| State | Selector | Appearance |
|---|---|---|
| Hover (control) | `:hover` | Lime tint |
| Hover (button) | `:hover` | Lifts up-left, shadow grows |
| Active (button) | `:active` | Presses down-right, shadow collapses |
| Focus | `:focus-visible` | 3px solid ring, 2–3px offset |
| Required | `:required` | 6px lime leading border |
| Valid | `:user-valid` | Green border, after interaction only |
| Invalid | `:user-invalid`, `[aria-invalid="true"]` | Orange border + tint |
| Readonly | `[readonly]` | Recessed ground, muted text |
| Disabled | `:disabled` | Grey fill, no shadow, `not-allowed` |
| Current page | `[aria-current]` | Lime pill |

---

## 6. Design tokens

All tokens live on `:root` as custom properties, prefixed `--ui-`.

### Color

| Token | Light | Dark |
|---|---|---|
| `--ui-color-background` | `#f5f8ce` | `#12140b` |
| `--ui-color-surface` | `#ffffff` | `#1d2013` |
| `--ui-color-text` | `#0b0b0b` | `#f2f6d8` |
| `--ui-color-muted` | `#4a4a44` | `#a6ab8e` |
| `--ui-color-border` | `#000000` | `#f2f6d8` |
| `--ui-color-primary` | `#d6f03a` | `#d6f03a` |
| `--ui-color-primary-text` | `#0b0b0b` | `#0b0b0b` |
| `--ui-color-accent` | `#22c55e` | `#35d06f` |
| `--ui-color-highlight` | `#f08fdc` | `#f08fdc` |
| `--ui-color-danger` | `#f1541b` | `#ff7a45` |
| `--ui-color-inverse` | `#0b0b0b` | `#f2f6d8` |
| `--ui-color-inverse-text` | `#f5f8ce` | `#12140b` |
| `--ui-color-disabled` | `#dedec8` | `#2d3120` |
| `--ui-color-focus` | `#000000` | `#d6f03a` |

### Typography

`--ui-font-sans` `--ui-font-mono`
`--ui-text-xs` `--ui-text-sm` `--ui-text-base` `--ui-text-lg` `--ui-text-xl`
`--ui-text-2xl` `--ui-text-3xl` `--ui-text-4xl`
`--ui-leading-tight` `--ui-leading-snug` `--ui-leading-normal`
`--ui-tracking-display` `--ui-tracking-tight` `--ui-tracking-wide`
`--ui-weight-normal` `--ui-weight-medium` `--ui-weight-bold` `--ui-weight-black`

`xl` and above are `clamp()`ed, so headings scale with the viewport without media
queries.

### Border, radius, shadow

`--ui-border-width` `--ui-border` `--ui-border-thick`
`--ui-radius-sm` `--ui-radius-md` `--ui-radius-lg` `--ui-radius-pill`
`--ui-shadow-sm` `--ui-shadow-md` `--ui-shadow-lg`

Shadows are hard offsets with **no blur** — `3px 3px 0 var(--ui-color-border)` — the
offset-frame effect from the reference.

### Spacing & layout

`--ui-space-1` … `--ui-space-8` (0.25rem → 4.5rem)
`--ui-measure` (paragraph width) `--ui-container` (body width)
`--ui-control-height` `--ui-transition`

---

## 7. Customization

Override tokens in your own stylesheet, **after** the link. Author CSS outside
`@layer` always beats everything the library defines, so no specificity fight and no
`!important`:

```css
:root {
    --ui-color-primary: #ff5c00;
    --ui-color-background: #fffdf5;
    --ui-radius-sm: 10px;
    --ui-shadow-sm: 0 2px 6px rgb(0 0 0 / 0.12);
    --ui-font-sans: "Inter", system-ui, sans-serif;
}
```

That alone re-themes every element. To restyle one element:

```css
button[type="submit"] {
    background: var(--ui-color-accent);
}
```

The library itself ships **no** font requests — `--ui-font-sans` is a grotesk-first
stack that degrades to the system font. The default stack already lists `Archivo`
first, so loading it is a single line in your `<head>` and no CSS change at all:

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;700;800&display=swap">
```

`demo/index.html` does exactly this — that is what makes it match the visual
reference. For any other family, point the token at it:

```css
:root { --ui-font-sans: "Inter", system-ui, sans-serif; }
```

### Cascade layers

```css
@layer reset, base, components;
```

| Layer | Contains |
|---|---|
| `reset` | Minimal UA normalisation — box model, margins, media, form inheritance |
| `base` | Tokens, document, typography, lists, media, links |
| `components` | Landmarks, nav, forms, buttons, tables, interactive, responsive |

Your unlayered CSS sits above all three.

---

## 8. Dark mode

Automatic, via `@media (prefers-color-scheme: dark)`. The reference already contained
an inverted surface (the black statistics bar), so dark mode reuses that relationship
rather than inventing a second design language: the ground goes dark olive, borders
and text go cream, and lime stays the accent.

To force one theme, pin the tokens yourself:

```css
:root { color-scheme: light; --ui-color-background: #f5f8ce; /* … */ }
```

---

## 9. Accessibility

* Every focusable element has a visible `:focus-visible` ring (3px solid, offset).
  `outline: none` is never used without a replacement.
* Controls are at least `2.75rem` (44px) tall — comfortable touch targets.
* Body text on the ground colour is ≈ 17:1 contrast; muted text ≈ 8:1; lime buttons
  use black text at ≈ 15:1. All well past WCAG AA.
* Checkbox and radio are restyled with `appearance: none` but keep native semantics,
  keyboard behaviour, and the `:checked` / `:indeterminate` / `:disabled` states.
* `:user-valid` / `:user-invalid` are used instead of `:valid` / `:invalid`, so an
  untouched required field is not shown as an error before the user types.
* Validity colour is never the only signal — border weight and fill change too.
* `prefers-reduced-motion: reduce` disables transitions and the button press
  translation.
* Nothing is hidden, reordered, or made unreachable by CSS.

---

## 10. Browser support

Evergreen Chrome, Edge, Firefox, and Safari. The library uses `@layer`, `:has()`,
`:is()`, `:where()`, `:user-invalid`, `:modal`, `color-mix()`, `clamp()`, and logical
properties — all baseline since 2023.

Older browsers still get readable, usable HTML: unsupported selectors are simply
dropped and the UA styles remain.

No polyfills, no vendor build, no PostCSS required.

---

## 11. File size

| File | Size | gzip | brotli |
|---|---|---|---|
| `default-ui.css` | 29.3 KB | — | — |
| `default-ui.min.css` | 21.4 KB | **4.5 KB** | **4.0 KB** |

One request. No JavaScript. No font files.

---

## Project structure

```
.
├── css/
│   ├── default-ui.css      # the source — edit this one
│   └── default-ui.min.css  # generated by build.sh, committed
├── demo/
│   ├── index.html          # every supported element, zero classes
│   └── plain.html          # same markup, no stylesheet
├── build.sh
├── README.md
└── LICENSE
```

## Build

`css/default-ui.css` is the source of truth. `css/default-ui.min.css` is generated
and committed, so consumers can hotlink it without a build step.

```bash
./build.sh
```

That minifies, verifies, and prints the size table. `build.sh` needs `python3` only
(`brotli` is optional and used for size reporting).

```bash
./build.sh --check
```

Exits non-zero if the committed `.min.css` no longer matches the source — useful as a
pre-commit hook or CI step.

The minifier is deliberately conservative: it strips comments and redundant whitespace
and nothing else. Quoted strings and `url()` data URIs are copied byte for byte, so the
inline SVG icons survive. It also verifies that the rule count and data-URI count are
unchanged, and warns if a class selector ever creeps into the source.

**After editing the CSS, run `./build.sh` and commit both files, then tag a new
version** — jsDelivr caches tags permanently, so never move an existing tag.

## License

MIT © Nu'man Nasyar MZ — see [LICENSE](LICENSE).
