# Handcoded UI Components — Penguin UI Replacement Index > **Scope**: Every handcoded UI component. > Each item maps to a [Penguin UI](https://github.com/SalarHoushvand/penguinui-components/tree/main) component that duplicates the same purpose with fewer lines and better accessibility. ## Vendoring convention The full library is now vendored locally at repo-root `penguinui-components/` (177 component `.html` files, moved there 2026-06-18). Read components from there — **NO** network/curl/WebFetch needed anymore. ### HARD RULE — read-only, never edit `penguinui-components/` is a read-only third-party library, **NOT our code**. Never edit, never `{% include %}`, never adapt in place. It is reference only; **copy markup OUT** of it and adapt at the use-site. When a Penguin UI component can replace a handcoded one: 1. Find the component in the local `penguinui-components/` directory. 2. Copy its markup **out** into the appropriate `assets/views/` location, adapting it where used (strip docs-only demo triggers, fix obvious upstream bugs, wire data bindings, map Penguin classes to our design tokens). Note the deviations in a comment next to the adapted copy. 3. Keep the original `penguinui-components/` file **untouched** — it stays as a byte-for-byte reference snapshot. 4. Rebuild Tailwind (`make css`) so any new utility classes get compiled. 5. Mark the section below as ✅ **DONE**. ### Why it's at repo root + the build guard Moved OUT of `assets/views/` to repo root because Tailwind v4 auto-detects sources from the project root — so `assets/css/app.css` carries `@source not "../../penguinui-components";` to explicitly exclude it from the build. If the build ever balloons, check that exclusion is intact. --- ## 0. Toast — ✅ DONE **Penguin UI: `toast-notification/stacking-toast-notification.html`** - Exact upstream mirror at `penguinui-components/toast-notification/stacking-toast-notification.html` (reference only) - Adapted/rendered copy lives inline in `assets/views/base.html` (demo triggers removed; the upstream dismiss-button `` quote bugs fixed) - The global `toast('message')` JS helper now dispatches the component's `notify` event (`{ variant: 'success', message }`), so existing callsites (`shop/show.html`, `shop/_card.html`) keep working unchanged. --- ## 1. Navbar — ✅ DONE **Penguin UI: `navbar/default-navbar.html`** - Exact upstream mirror at `penguinui-components/navbar/default-navbar.html` (reference only) - **Link treatment** adopted from Penguin (matches the already-ported sidebars): the desktop nav links lost the pill-hover (`hover:bg-surface-alt` + `px-3 py-1.5`) for Penguin's text-only `underline-offset-2 hover:text-primary focus-visible:underline focus:outline-hidden`, active (`aria-current=page`, set by `markActiveNav()` via `data-nav`) = `font-semibold` + primary. Centralized into a `ui::nav_link(label, href, data_nav, variant, attrs)` macro in `macros/ui.html` (variant ∈ default | warning admin | danger). Logout stays an inline `
`). - Adopts Penguin's dropdown menu container + item treatment. Deviations: kept our gear icon-only trigger and core-Alpine open/@click.outside toggle (upstream's `x-trap`/`$focus` need the Alpine Focus plugin we don't bundle); item hover uses `bg-primary/5` for consistency with the rest of the UI. --- ## 5. Country / Phone Combobox — ⛔ WON'T PORT (conscious deviation) **Penguin UI: `combobox/phone-number-input-with-country-code-dropdown.html`** > **Decision 2026-06-18:** keep our lightweight hand-rolled comboboxes; do NOT > port the Penguin one. The Penguin combobox depends on the **Alpine Focus > plugin** (`x-trap`, `$focus.wrap().next()`) which this build does not bundle > (same reason the settings dropdown & category accordion deviate), ships a > 240-country `allOptions` list + `flagcdn.com` remote flag images, and a search > field — far heavier than our deliberate 9-prefix / 6-country editable inputs. > Our versions already use the Penguin design tokens (`bg-surface`, `border-outline`, > `focus:outline-primary`) and emoji flags, so they look on-brand. Net: porting > would add a JS dependency and external image loads for negative UX value. > Revisit only if we adopt the Alpine Focus plugin project-wide. | # | Location | What it is | Size | |---|----------|------------|------| | 9 | `assets/views/shop/checkout.html:49-74` | Phone prefix combobox (`+421`, `+420`, …, `+33`) | ~25 lines | | 10 | `assets/views/shop/checkout.html:102-127` | Country combobox (SK, CZ, AT, DE, PL, HU) | ~26 lines | **Details for #9:** - Alpine `x-data` with `prefix`, `prefixOpen`, `opts` array of `{ v, l }` (9 country codes) - Manual `filtered` computed property - Inline chevron SVG that rotates via `:class="prefixOpen && 'rotate-180'"` - Dropdown list with `