30 KiB
Handcoded UI Components — Penguin UI Replacement Index
Scope: Every handcoded UI component. Each item maps to a Penguin UI 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:
- Find the component in the local
penguinui-components/directory. - 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. - Keep the original
penguinui-components/file untouched — it stays as a byte-for-byte reference snapshot. - Rebuild Tailwind (
make css) so any new utility classes get compiled. - 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<svg>quote bugs fixed) - The global
toast('message')JS helper now dispatches the component'snotifyevent ({ variant: 'success', message }), so existing callsites (shop/show.html,shop/_card.html) keep working unchanged.
1. Navbar — PENDING
Penguin UI: navbar/
Priority: MEDIUM | ~143 lines across2 sites. Penguin match:
navbar/default-navbar.html— provides mobile-responsive structure, hamburger animation, and link treatment that would replace our hand-rolled<nav>. Watch out: tight integration needed for cart badge, settings dropdown partial, language switcher, and Alpine mobile toggle — don't lose any of those.
| # | Location | What it is | Size |
|---|---|---|---|
| 1 | assets/views/base.html:63-191 |
Full site navbar: brand, desktop nav links, cart icon+badge, settings dropdown, mobile hamburger → mobile panel | ~130 lines |
| 2 | assets/views/admin/base.html:102-114 |
Admin top bar: hamburger toggle + breadcrumb text | ~13 lines |
Details for #1 (site navbar):
- Brand/logo:
base.html:74-77— plain<a>with text - Desktop nav links:
base.html:80-92—<ul>with 4–5 items, manualaria-currentrouting - Cart icon + badge:
base.html:96-109— hand-rolled SVG cart icon + an Alpinex-databadge that readsdocument.cookiedirectly - Settings dropdown:
base.html:110-162— gear-icon trigger + language-switcher<form>+ theme tristate (system/light/dark) - Mobile hamburger:
base.html:164-172— hamburger SVG button - Mobile menu panel:
base.html:175-190— dropdown<ul>with duplicated nav links
Penguin navbar variants: default-navbar.html, with-call-to-action.html, with-search.html, with-user-profile.html
2. Sidebar (Admin) — ✅ DONE
Penguin UI: sidebar/simple-sidebar.html
- Exact upstream mirror at
penguinui-components/sidebar/simple-sidebar.html(reference only) - Adapted at use-site in
assets/views/admin/base.html: the nav links + bottom exit/logout now use Penguin's link treatment (hover:bg-primary/5,underline-offset-2 focus-visible:underline focus:outline-hidden) and the subtle active state (bg-primary/10+text-on-surface-strong) mapped onto ourdata-nav/aria-currentsomarkActiveNav()still drives it. - The fixed-rail translate-X show/hide mechanics + mobile overlay (#4) are layout scaffolding, kept as-is. Icons were intentionally not added (no verified icon set yet) — possible follow-up.
3. Sidebar (Category Accordion) — ✅ DONE
Penguin UI: sidebar/sidebar-with-collapsible-menus.html
- Exact upstream mirror at
penguinui-components/sidebar/sidebar-with-collapsible-menus.html(reference only) - Adapted at use-site in
assets/views/shop/_sidebar.html: Penguin link treatment + active state + chevron-down rotation (rotate-180); child items now sit in a bordered/indented list instead of the oldpadding-left:28px+↳. Kept our htmx partial, data-drivencategory_groups, auto-expandx-init, anddata-nav/markActiveNav()active routing. - Deviations: group row keeps our link + chevron-toggle split (categories are
navigable, not just expandable); uses
x-show/x-transitioninstead of upstream'sx-collapse(that Alpine plugin isn't bundled in our build). - The
<aside>drawer + mobile overlay (#6) inbase.htmlare layout scaffolding, kept as-is.
4. Dropdown (Settings) — ✅ DONE
Penguin UI: dropdowns/dropdown-with-click.html
- Exact upstream mirror at
penguinui-components/dropdowns/dropdown-with-click.html(reference only) - De-duplicated: the ~103-line copy-paste is now one shared partial
assets/views/partials/settings_dropdown.html, included by bothbase.htmlandadmin/base.html(each host keeps its own positioning wrapper<div x-data="{ open:false }" class="relative [ml-auto]">). - 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/$focusneed the Alpine Focus plugin we don't bundle); item hover usesbg-primary/5for 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-countryallOptionslist +flagcdn.comremote 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-datawithprefix,prefixOpen,optsarray of{ v, l }(9 country codes) - Manual
filteredcomputed property - Inline chevron SVG that rotates via
:class="prefixOpen && 'rotate-180'" - Dropdown list with
<template x-for>and@clickselection
Details for #10:
- Same pattern as #9 but with translate-able country names (6 countries)
- Includes
+421prefix shortcut
6. Product Card — ✅ DONE
Penguin UI: card/ecommerce-product-card.html
- Exact upstream mirror at
penguinui-components/card/ecommerce-product-card.html(reference only) - Adapted/rendered copy is
assets/views/shop/_card.html:<article>shell + Penguin image/title/price layout and the cart-icon add-to-cart button, wired to our product data + i18n + htmxhx-postadd-to-cart +toast(). Demo-only rating stars, hardcoded content andmax-w-sm(fights the shop grid) were dropped; whole card links to the product page; out-of-stock badge kept.
7. Product Image Gallery — ✅ DONE
Penguin UI: carousel/default-carousel.html
- Exact upstream mirror at
penguinui-components/carousel/default-carousel.html(reference only) - Adapted at use-site in
assets/views/shop/show.html: added Penguin's overlay prev/next arrow buttons (bg-surface/40rounded, verbatim chevron SVGs) andx-transition.opacity.duration.300msfade between images. Addedprev()/next()with wraparound to the galleryx-data; arrows + transitions only render whenimages | length > 1. - Deviations: kept our product thumbnail strip (more useful than carousel
dot indicators for a product page) and our 0-based
activeindex (Penguin uses 1-basedcurrentSlideIndex); main images switched toabsolute inset-0so the fade cross-dissolves inside theaspect-squareframe. New i18n keysgallery-prev/gallery-next(sk + en) for the arrowaria-labels.
8. Radio-Button Groups — ✅ DONE
Penguin UI: radio/radio-with-container.html
- Exact upstream mirror at
penguinui-components/radio/radio-with-container.html(reference only) - New
ui::radio(name, value, id, checked, attrs, extra)macro inmacros/ui.htmlemits only the Penguin custom radio-dot<input>(verbatimappearance-nonebefore:dot +checked:bg-primary). Callers keep their own card-style<label>wrapper — we kept ourhas-[:checked]:border-primarycard highlight, which is richer than Penguin's plainbg-surface-altcontainer.
- Adopted at
shop/checkout.html: both payment radios (ui::radiowithattrs='required x-model="paymentMethod"'). The carrier radio (in the{% for m in shipping_methods %}loop) keeps the same Penguin dot class inline because its@change="carrier='{{ m.code }}'; …"mixes nested single+double quotes that can't pass through a Tera macro arg (same convention as the cart qty input). Nativetext-primaryradios are gone.
9. Checkbox — ✅ DONE
Penguin UI: checkbox/default-checkbox.html
- Exact upstream mirror at
penguinui-components/checkbox/default-checkbox.html(reference only) ui::checkbox(name, label, id, value="on", checked, attrs)macro inmacros/ui.html(full Penguin control: custom box + check-icon + label,has-checked:/peervariants).- Adopted: product/category "Published" + shipping "Enabled".
10. Text Input — ✅ DONE
Penguin UI: text-input/default-text-input.html
- Exact upstream mirror at
penguinui-components/text-input/default-text-input.html(reference only) ui::input(name, type, id, value, placeholder, required, autocomplete, attrs, extra, width="w-full")macro — verbatim Penguin classes (bg-surface-alt,focus-visible:outline-*). Adopted at every text/email/number/password input: login (2), checkout (email, name, phone, address, city, zip), product form (6), category form (3), product detail quantity, shipping price (width="w-28").- The cart-body quantity input keeps its complex
@changehandler inline with the same Penguin classes (mixed single/double quotes can't pass through a macro arg). - Note: padding is Penguin's
px-2 py-2(waspx-3) and bg isbg-surface-alt(wasbg-surface) — the real Penguin look.
11. Textarea — ✅ DONE
Penguin UI: text-area/default-textarea.html
- Exact upstream mirror at
penguinui-components/text-area/default-textarea.html(reference only) ui::textarea(name, id, value, rows, placeholder, required, attrs, extra)macro.- Adopted: checkout note, product & category description.
12. Select/Dropdown (Native) — ✅ DONE
Penguin UI: select/default-select.html
- Exact upstream mirror at
penguinui-components/select/default-select.html(reference only) - Adopted inline (3 sites: product category, category parent, order status) — Penguin
appearance-noneselect onbg-surface-altwrapped inrelativewith the chevron SVG. Inline rather than a macro because the<option>set is caller-specific.
13. File Input — ✅ DONE
Penguin UI: file-input/default-file-input.html
- Exact upstream mirror at
penguinui-components/file-input/default-file-input.html(reference only) ui::file_input(name, id, accept, attrs, extra)macro (verbatim Penguinfile:styling).- Adopted: product & category image upload.
14. Table — PENDING
Penguin UI: table/ (5 variants)
Priority: HIGH | ~196 lines across5 instances. The same class structure is copy-pasted5 times. Penguin match:
table/default-table.htmlfor basic tables,table-with-action.htmlfor row actions. Consider aui::table_header()+ui::table_row()macro.
| # | Location | What it is | Size |
|---|---|---|---|
| 34 | assets/views/admin/orders/index.html:11-36 |
Orders table: number, customer, status pill, total, "View" link | ~26 lines |
| 35 | assets/views/admin/orders/show.html:20-44 |
Order items table: product, quantity, line total + tfoot summary | ~25 lines |
| 36 | assets/views/admin/catalog/products.html:20-70 |
Products table: image+name+category, price, stock, status pill, edit/view/delete actions | ~51 lines |
| 37 | assets/views/admin/catalog/categories.html:20-59 |
Categories table: tree-indented name, product count, status pill, edit/delete | ~40 lines |
| 38 | assets/views/shop/_cart_body.html:6-59 |
Cart table: product link, price, quantity input, line total, remove button + tfoot total | ~54 lines |
Pattern: Every table uses the same class structure:
<table class="w-full text-left text-sm">
<thead class="border-b border-outline bg-surface-alt text-xs uppercase tracking-wide text-on-surface/70">
<tbody class="divide-y divide-outline">
<tr class="hover:bg-surface-alt">
This is copy-pasted 5 times.
15. Alert / Error Banner — ✅ DONE
Penguin UI: alert/default-alert.html
- Exact upstream mirror at
penguinui-components/alert/default-alert.html(reference only) - Adapted into the
ui::alert_danger(message, extra="")macro inassets/views/macros/ui.html(compact one-line danger alert + danger icon). - Adopted at both sites:
admin/login.html(login error) andadmin/orders/show.html(ship error).
16. Badge / Status Pill — ✅ DONE
Penguin UI: badge/soft-color-badge.html
- Exact upstream mirror at
penguinui-components/badge/soft-color-badge.html(reference only) - Adapted into the
ui::badge(label, variant)macro inassets/views/macros/ui.html(variants: success | danger | warning | info | primary | neutral). - Adopted at the status-pill sites: "Auth" badge (
admin/login.html), order status (orders/index.html, neutral), Published/Draft pills (products.html+categories.html, success/neutral). - Intentionally left inline (not soft-color pills): the cart item-count notification
badge in
base.html(count bubble, a different Penguin badge type) and the block-style "out of stock" notice in_card.html.
17. Buttons — ✅ DONE
Penguin UI: buttons/default-button.html, outline-button.html, ghost-button.html, button-with-icon.html
- Exact upstream mirrors at
penguinui-components/buttons/*.html(reference only). - Macros in
assets/views/macros/ui.html:ui::button(label, variant="primary", type, href, attrs, extra, icon, size="px-4 py-2 text-sm")andui::icon_button(icon, variant="ghost-secondary", aria_label, attrs, …). The per-variant class strings are the verbatim Penguin variants (solidprimary|secondary|danger|success|warning|info,outline-*,ghost-*) — onlyinline-flex items-center justify-center gap-2is added so<a>/w-full/iconrender, and upstream'stext-onDanger/text-onSuccess… token typos are fixed to our realtext-on-*tokens.href→<a>else<button>;attrsis raw (htmx /:disabled/ name / value);iconis a raw<svg>rendered before the label (Penguin button-with-icon). - Sizes are NOT normalized:
sizedefaults to Penguin'spx-4 py-2 text-smbut each call site that differed keeps it (px-3 py-2form-header cancels & order back,px-5 py-2add-to-cart / cart-checkout / order-confirmed continue,px-6 py-2.5checkout place-order,px-3 py-1.5 text-xstable actions). - Adopted across every standard filled/outline/submit button: login, product &
category forms (save / cancel =
outline-secondary), products/categories "new" + empty-state CTAs, orders detail (back/ship/status), shipping save, cart (continue/checkout/empty), checkout place-order (:disabledviaattrs), product detail add-to-cart, order-confirmed continue. - Icon-only buttons now use
ui::icon_button(icon, variant="ghost-secondary", aria_label, attrs, …)— Penguin ghost treatment, square. Converted: settings gear, both hamburgers (site + admin), admin sidebar toggle, mobile category toggle. The cart link (livex-initbadge) and the category-accordion chevron keep the same Penguin ghost classes inline only because their markup mixes single+double quotes that can't be passed through a Tera macro arg — visually identical toicon_button. - Table row-actions (
edit/view/delete/View/label) →ui::buttonoutline-secondary/outline-dangeratsize="px-3 py-1.5 text-xs"; cart "Remove" →ghost-danger; card add-to-cart →ui::buttonwith the carticon. - Still genuinely not this component (tracked elsewhere): toast dismiss/Reply buttons (part of the vendored toast mirror, already Penguin), settings dropdown menu items (Penguin dropdown items), gallery thumbnail buttons (carousel), sidebar logout/exit (Penguin sidebar link treatment), and navbar nav-menu links/logout (belong to §1 Navbar). The file-input button is §13.
Gotcha for future macro use: Tera renders
{% include %}in the includer's macro scope, so a template that includes a partial which callsui::must also{% import "macros/ui.html" as ui %}itself (seeshop/cart.html→shop/_cart_body.html). In an{% extends %}child the import must sit directly after{% extends %}with no comment/content before it.
18. Toggle / Switch — LOW PRIORITY (de-duplication, not replacement)
Penguin UI: toggle/ (3 variants)
Priority: LOW | ~36 lines,100% duplicated between
base.htmlandadmin/base.html. This is JavaScript theme-switching logic (applyTheme,setTheme,matchMedia), not a CSS toggle component. Penguin'stoggle/default-toggle.htmlis a visual on/off switch — not applicable here. Action: de-duplicate the JS into a shared partial rather than porting.
| # | Location | What it is | Size |
|---|---|---|---|
| 53 | assets/views/base.html:13-30 |
Theme toggle (dark/light/system) — inline <script> JavaScript |
~18 lines |
| 54 | assets/views/admin/base.html:13-30 |
Exact duplicate of the theme toggle JS | ~18 lines |
Details:
applyTheme(),setTheme(),currentTheme()— reads/writeslocalStoragematchMedia('prefers-color-scheme: dark')listener- All hand-written vanilla JS, duplicated twice (36 lines total)
19. Inline SVG Icons — LOW PRIORITY
Penguin UI: none (Penguin uses Heroicons-equivalent inline SVGs)
Priority: LOW | ~50 lines,8 distinct icons. Penguin doesn't provide an icon library. Icons are already inlined where used. Possible follow-up: extract into a
ui::icon(name)macro for dedup, but this is purely cosmetic — no functional gain.
| # | Location | Icon | Occurrences |
|---|---|---|---|
| 55 | base.html:70-72,168-170 |
Hamburger (3-line menu) | 2 |
| 56 | base.html:104-105 |
Shopping cart | 1 |
| 57 | base.html:116-121 |
Gear/cog (settings) | 1 |
| 58 | base.html:220-221 |
Checkmark (toast success) | ✅ removed — now in vendored toast component |
| 59 | checkout.html:62-64,115-117 |
Chevron-down (dropdown arrow) | 2 |
| 60 | _sidebar.html:30-33 |
Chevron-right (accordion expand) | 1 |
| 61 | admin/base.html:106-108 |
Hamburger (admin sidebar toggle) | 1 |
| 62 | admin/base.html:121-125 |
Gear/cog (admin settings) | 1 |
All are raw inline <svg> with hardcoded <path d="..."> — no icon library, no partials.
20. Empty State — LOW PRIORITY
Penguin UI: no dedicated empty-state component
Priority: LOW | ~22 lines across5 sites. These are simple
<div>messages, often with a CTA button already usingui::button. Nothing to port — already consistent with project styling.
| # | Location | What it is | Size |
|---|---|---|---|
| 63 | assets/views/admin/orders/index.html:38-39 |
"No orders" message | ~2 lines |
| 64 | assets/views/admin/catalog/products.html:72-78 |
"No products" with CTA button | ~7 lines |
| 65 | assets/views/admin/catalog/categories.html:61-67 |
"No categories" with CTA button | ~7 lines |
| 66 | assets/views/shop/_cart_body.html:67-70 |
"Cart empty" with CTA button | ~4 lines |
| 67 | assets/views/shop/_sidebar.html:58-59 |
"No categories" message | ~2 lines |
21. Dashboard Navigation Cards — LOW PRIORITY
Penguin UI: card/default-card.html or card/card-with-button.html
Priority: LOW | ~16 lines. Already uses card styling (
rounded-radius border border-outline hover:border-primary). Penguin'sdefault-card.htmladds a structured header/body layout — adopt if cards ever grow beyond a title+description link.
| # | Location | What it is | Size |
|---|---|---|---|
| 68 | assets/views/admin/index.html:12-27 |
3 dashboard link cards (Products, Categories, Orders) | ~16 lines |
Details:
- Each card is an
<a>styled with border, hover effect, and nested title+description - Same hover pattern:
hover:border-primary
22. Checkout Order Summary — LOW PRIORITY
Penguin UI: card/
Priority: LOW | ~29 lines. Already uses card-like styling with
tabular-nums. All internal buttons useui::button. The only handcoded part is the outer<div>wrapper and line-item layout. Penguin doesn't have an ecommerce-specific summary component.
| # | Location | What it is | Size |
|---|---|---|---|
| 69 | assets/views/shop/checkout.html:190-218 |
Cart summary aside: item list, subtotal, shipping, total, place-order button | ~29 lines |
Details:
- Item list with name × quantity + line total
- Subtotal + shipping + total with
tabular-nums - Dynamic shipping price from Alpine
carrierPrice - Disabled submit button when
!canSubmit
23. Login Card — LOW PRIORITY
Penguin UI: card/default-card.html
Priority: LOW | ~56 lines. Already fully uses Penguin macros inside:
ui::input,ui::button,ui::badge,ui::alert_danger. Only the outer card wrapper (border, bg-surface-alt, shadow-sm) is handcoded. Adoptingdefault-card.htmlwould add visual polish but little functional gain.
| # | Location | What it is | Size |
|---|---|---|---|
| 70 | assets/views/admin/login.html:6-61 |
Full login form: header with auth badge, email + password inputs, error alert, submit button | ~56 lines |
24. Checkout Fieldset Cards — LOW PRIORITY
Penguin UI: card/
Priority: LOW | ~142 lines across4 fieldsets. Already uses card styling (
rounded-radius border border-outline bg-surface p-6) and all internal form controls use Penguin macros (ui::input,ui::textarea,ui::button). Only the<fieldset>+<legend>wrapping is handcoded. Low value in replacing — fieldset semantics are correct here.
| # | Location | What it is | Size |
|---|---|---|---|
| 71 | assets/views/shop/checkout.html:34-79 |
Contact info fieldset (email, name, phone+prefix) | ~46 lines |
| 72 | assets/views/shop/checkout.html:82-130 |
Shipping address fieldset (address, city, zip, country) | ~49 lines |
| 73 | assets/views/shop/checkout.html:133-165 |
Carrier selection fieldset | ~33 lines |
| 74 | assets/views/shop/checkout.html:167-180 |
Payment method fieldset | ~14 lines |
Each fieldset uses <fieldset> + <legend> with the same rounded-radius border border-outline bg-surface p-6 styling.
25. Order Detail Info Panel — LOW PRIORITY
Penguin UI: card/
Priority: LOW | ~64 lines across3 panels. Already card-styled (
rounded-radius border border-outline bg-surface p-5) with Penguin macros inside. Deviating from this simple structure would make the dense info layout harder to scan.
| # | Location | What it is | Size |
|---|---|---|---|
| 75 | assets/views/admin/orders/show.html:49-77 |
Customer + shipping + payment info panel | ~29 lines |
| 76 | assets/views/admin/orders/show.html:79-103 |
Fulfillment panel (tracking, label link, ship button) | ~25 lines |
| 77 | assets/views/admin/orders/show.html:106-115 |
Status update form panel | ~10 lines |
26. Shipping Method Settings Row — LOW PRIORITY
Penguin UI: card/
Priority: LOW | ~21 lines. Already fully uses Penguin macros:
ui::input,ui::checkbox,ui::button. The card wrapper is the same pattern as other admin panels. Nothing to port.
| # | Location | What it is | Size |
|---|---|---|---|
| 78 | assets/views/admin/shipping/index.html:14-34 |
Per-carrier settings: name label, price input, enabled checkbox, save button | ~21 lines |
27. Product/Category Form Wrapper — LOW PRIORITY
Penguin UI: card/
Priority: LOW | ~150 lines across2 forms. Already fully uses Penguin macros:
ui::input,ui::textarea,ui::select,ui::file_input,ui::checkbox,ui::button. The<form>card wrapper is the same border/bg pattern. Penguin doesn't have a form-specific layout component.
| # | Location | What it is | Size |
|---|---|---|---|
| 79 | assets/views/admin/catalog/product_form.html:15-99 |
Full product edit/create form with all fields | ~84 lines |
| 80 | assets/views/admin/catalog/category_form.html:15-81 |
Full category edit/create form with all fields | ~66 lines |
Both are wrapped in a single card-style <form>.
Porting Roadmap (priority order)
Phase 1 — HIGH (direct Penguin matches, clear win)
| # | Component | Penguin match | Est. effort | Lines saved |
|---|---|---|---|---|
| ⛔ WON'T PORT — needs Alpine Focus plugin; our lightweight version kept | — | — | ||
✅ DONE — carousel/default-carousel.html |
Small | ~19 | ||
✅ DONE — radio/radio-with-container.html |
Small | ~47 | ||
| 14 | Table | table/default-table.html |
Medium | ~196 |
Phase 2 — MEDIUM (good match, more integration risk)
| # | Component | Penguin match | Est. effort | Lines saved |
|---|---|---|---|---|
| 1 | Navbar | navbar/default-navbar.html |
Large | ~143 |
Phase 3 — LOW (mostly already Penguin, or no good match)
| # | Component | Action |
|---|---|---|
| 18 | Toggle/Switch | De-duplicate JS into shared partial (not a Penguin port) |
| 19 | Inline SVG Icons | Optional: extract ui::icon(name) macro |
| 20 | Empty State | Already fine — nothing to port |
| 21 | Dashboard Cards | Adopt card/default-card.html if cards grow |
| 22 | Checkout Summary | Already fine — nothing to port |
| 23 | Login Card | Already fine — only outer wrapper is handcoded |
| 24 | Checkout Fieldsets | Already fine — only <fieldset> wrapper is handcoded |
| 25 | Order Info Panels | Already fine — only card wrappers are handcoded |
| 26 | Shipping Settings Row | Already fully uses Penguin macros |
| 27 | Form Wrappers | Already fully uses Penguin macros |
Already DONE (15 of 27)
| # | Component |
|---|---|
| 0 | Toast |
| 2 | Sidebar (Admin) |
| 3 | Sidebar (Category Accordion) |
| 4 | Dropdown (Settings) |
| 6 | Product Card |
| 7 | Image Gallery |
| 8 | Radio Groups |
| 9 | Checkbox |
| 10 | Text Input |
| 11 | Textarea |
| 12 | Select/Dropdown |
| 13 | File Input |
| 15 | Alert / Error Banner |
| 16 | Badge / Status Pill |
| 17 | Buttons |
Remaining real ports: just #14 Table (~196 lines, Medium) and #1 Navbar (~143 lines, Large). #5 Combobox is a conscious WON'T-PORT (Alpine Focus plugin dependency). The Phase-3 items are already internally Penguin-adapted or have no applicable component.
Summary
| # | Component | Penguin UI Directory | Status | Lines |
|---|---|---|---|---|
| 0 | Toast | toast-notification/ |
✅ DONE | — |
| 1 | Navbar | navbar/ |
PENDING (MED) | ~143 |
| 2 | Sidebar (admin) | sidebar/ |
✅ DONE | ~46 |
| 3 | Sidebar (category accordion) | sidebar/ |
✅ DONE | ~62 |
| 4 | Dropdown (settings) | dropdowns/ |
✅ DONE | ~103 |
| 5 | Country/Phone combobox | combobox/ |
⛔ WON'T PORT | ~51 |
| 6 | Product card | card/ |
✅ DONE | ~30 |
| 7 | Image gallery | carousel/ |
✅ DONE | ~19 |
| 8 | Radio groups | radio/ |
✅ DONE | ~47 |
| 9 | Checkbox | checkbox/ |
✅ DONE | ~15 |
| 10 | Text input | text-input/ |
✅ DONE | ~146 |
| 11 | Textarea | text-area/ |
✅ DONE | ~10 |
| 12 | Select | select/ |
✅ DONE | ~23 |
| 13 | File input | file-input/ |
✅ DONE | ~12 |
| 14 | Table | table/ |
HIGH | ~196 |
| 15 | Alert/Error | alert/ |
✅ DONE | ~9 |
| 16 | Badge/Pill | badge/ |
✅ DONE | ~17 |
| 17 | Button | buttons/ |
✅ DONE | ~200+ |
| 18 | Toggle (theme) | toggle/ |
LOW (dedup) | ~36 |
| 19 | Inline SVG icons | N/A | LOW | ~50 |
| 20 | Empty state | N/A | LOW | ~22 |
| 21 | Dashboard cards | card/ |
LOW | ~16 |
| 22 | Checkout summary | card/ |
LOW | ~29 |
| 23 | Login card | card/ |
LOW | ~56 |
| 24 | Checkout fieldsets | card/ |
LOW | ~142 |
| 25 | Order info panels | card/ |
LOW | ~64 |
| 26 | Shipping settings row | card/ |
LOW | ~21 |
| 27 | Form wrappers | card/ |
LOW | ~150 |
Status: 15 of 27 components fully ported to Penguin UI. Only 2 real ports remain — #14 Table (HIGH) and #1 Navbar (MED). #5 Combobox is a conscious WON'T-PORT (Alpine Focus plugin dependency). The remaining Phase-3 items are already internally Penguin-adapted or have no applicable match.