TimonBozzApps/design-system-3gs

The iPhone 3GS / iOS 3 glossy UI as a React component library — 23 components, design tokens, dark + classic light themes. Plus: paste any URL and see that site as a 2009 iPhone app.

TypeScript

0

32 commits

updated Sep 23, 2026

See the code

See what people are saying

SourceMessageScoreDate

I recreated the iPhone 3GS (iOS 3) UI as a React component library (r/reactjs)

23 components: nav bar, tab bar, grouped tables, the toggle switch, the spinning picker, the on-screen keyboard, HUDs, action sheets, a date picker, ... all built on one "gel" recipe: a vertical gradient with a hard highlight split at 50%, a 1px black outline and a light inner rim. Everything is…

1

Sep 23, 2026

README

3GS UI

Live showcase → design-system-3gs.vercel.app · npm: @3gs/ui

A React design system that reproduces the iPhone 3GS / iOS 3 look — glossy "gel" skeuomorphism — recast in dark mode. Vertical gradients with the hard 50 % highlight, 1 px black outlines with an inner light rim, embossed type, pinstripe grouped tables, the blue ON/OFF switch, the black glass tab bar. Icons come from lucide and are tinted through an SVG gradient so they get the same glass treatment.

Paste any URL into the showcase and it re-renders that website as a 2009 iPhone app — navigable, with the site's own content, search and forms. Share one at /p/news.ycombinator.com.

packages/ui     @3gs/ui — the component library (React 18/19, TypeScript, plain CSS tokens)
apps/site       the showcase site (Vite) — every component inside a 320×480 phone frame

Components (v0.1.0)

ComponentWhat it is
ButtonGel push buttons — default / primary (blue) / destructive (red), 3 sizes, block
SwitchThe 94×27 ON/OFF slider with the sliding blue/dark strip and silver knob
TextField, SearchFieldSunken inputs with inline labels, clear button, and the pill search bar
NavigationBar, BarButton44 px black glass top bar, centered title, pointed back button
TabBar, TabBarItem49 px bottom bar with gradient icons, selected state, red badges
List, ListItemGrouped / plain table views: icon tiles, chevrons, detail text, headers, footers
AlertThe deep-blue modal alert with the top gloss and stacked / paired gel buttons
SegmentedControl, SegmentGel bar cut into equal segments, selected one sunken blue
SliderNative range input styled as the volume slider: blue fill, silver knob, end icons
ActionSheetBottom sheet that slides up: silver / red / dark-cancel gel buttons
PickerThe spinning wheel — scroll-snap drums, cylinder shading, glass selection bar
ProgressBar, ActivityIndicatorBlue gel progress bar and the 12-spoke stepped spinner
StatusBarThe 20 px strip: signal bars, 3G / EDGE / Wi-Fi, clock, Bluetooth, gel battery
Toolbar, ToolbarButton, ToolbarSpacer, ToolbarTitleBottom black bar with plain glossy icon buttons and centred status text
PageControlThe dots — tap a half to page, arrow keys, clickable dots
PopoverThe iPad 3.2 dark bubble with an arrow; auto-flips and clamps, portal or contained
HUDTranslucent black square: spinner / progress / check / error / text toast, auto-dismiss
KeyboardDark alert-style QWERTY with letters / numbers / symbols, shift + caps, key popup, delete repeat, value/onChange binding
DatePickerUIDatePicker presets on the wheel: date / time / dateTime, minute intervals, 12/24 h
ModalSheetFull-screen modal that slides up with its own Cancel / title / Done bar
StepperJoined − / + gel control for list rows, hold-to-repeat, wraps, value well
SearchBarUISearchBar: search pill in a bar with a sliding Cancel and scope buttons
NotificationBannerBlack-glass banner dropping from the top: icon tile, action, swipe-up to dismiss
Icon, BadgeFoundation: glossy lucide wrapper and the red gel badge

Use

pnpm add @3gs/ui lucide-react
import "@3gs/ui/styles.css";
import { NavigationBar, BarButton, List, ListItem, Icon } from "@3gs/ui";
import { Gamepad2 } from "lucide-react";

<div className="gs-root">
  <NavigationBar title="Categories" left={<BarButton variant="back">Featured</BarButton>} />
  <List>
    <ListItem iconTile icon={<Icon icon={Gamepad2} />} title="Games" accessory="chevron" onClick={…} />
  </List>
</div>

Wrap your app (or any subtree) in .gs-root to get the font, colours and black background. Tokens are CSS custom properties prefixed --gs- (packages/ui/src/tokens/tokens.css); override them on .gs-root to retint.

Themes

Dark (the default) is the black-glass take. data-theme="light" switches a subtree to the classic iOS 3 look — blue-gray bars, white cells on the pinstripe, black embossed text:

<div className="gs-root" data-theme="light">…</div>

The tab bar, alerts, HUDs and popovers stay dark in both themes, as they did on the device. Under the hood the light theme only redefines tokens (packages/ui/src/tokens/theme-light.css); components read semantic tokens such as --gs-gradient-bar, --gs-gradient-neutral, --gs-text-on-bar and --gs-text-emboss, so a third theme is one more file of overrides. The showcase's sidebar switch flips every phone screen between the two.

Localization and RTL

Every built-in string (Cancel, Done, space, "Page 2 of 5", aria-labels…) comes from GsProvider; explicit props still win:

<GsProvider locale="de-DE" strings={{ cancel: "Abbrechen", done: "Fertig", on: "EIN", off: "AUS" }}>
  <App />
</GsProvider>

DatePicker takes month / weekday names from the provider's locale. Layout uses logical properties throughout, so dir="rtl" on .gs-root mirrors the back button, chevrons, switches, slider fills and badges (the keyboard stays QWERTY). Overlays that portal to document.body (Alert, ActionSheet, HUD, Popover, ModalSheet) inherit the document's direction — pass dir to them or render them contained.

Props reference

The showcase's per-component prop tables are generated from the TypeScript types (pnpm propsapps/site/src/generated/props.json), including JSDoc descriptions and defaults, so they can't drift from the code.

Publishing

Releases use changesets: add one with pnpm changeset, and the Release workflow opens a "Version Packages" PR that publishes @3gs/ui on merge via npm trusted publishing (OIDC — no token; the publisher is TimonBozzApps/design-system-3gs / release.yml). The job runs only while the repository variable ENABLE_NPM_RELEASE is true. Locally: pnpm changeset:version && pnpm release. pnpm pack:check lists exactly what ships.

"Try it on your site"

The showcase can rebuild any public website as a 2009 iPhone app: /api/preview?url=<site> (a Vercel Function in api/) fetches the page with an SSRF-guarded fetcher (private / link-local / metadata addresses rejected on every redirect hop, 8 s timeout, 4 MB read cap), extracts title, icons, OG image, nav links, headings, CTAs and search forms, and maps them to a ScreenSpec (api/_lib/spec.ts) that the site renders with the library. The phone is navigable: tabs and rows fetch that page and push real screens (with a Back button), and every page carries its actual content — paragraphs, bullet lists, prices and stats, FAQ pairs, pull quotes, in-page images and code — rendered as iOS grouped-table cells. Feed pages (Hacker News, changelogs, blog indexes) are detected structurally and become a news-app list: headline plus a "382 points · 101 comments" meta line. Export a PNG, copy the generated JSX, or share /p/<site>: a standalone page with nothing but the phone, its own unfurl card (/api/share injects the meta tags, /api/image serves the site's image). Results are cached (1 h) and rate-limited (12/min/IP). Deep link: ?site=vercel.com. The mapper is a pure function so an AI-designed one can drop in later. Locally, pnpm dev serves the same endpoint through a Vite middleware; node scripts/preview-smoke.mjs runs the positive/negative cases.

Analytics (opt-in)

The showcase can report anonymous usage to PostHog — cookieless, DNT-respected, no session recording. It is off unless VITE_POSTHOG_KEY is set at build time (Vercel → Environment Variables); without it the SDK isn't even bundled. The key is PostHog's public, write-only project token (phc_…), which is designed to ship in browsers; nothing secret is needed or committed — see apps/site/.env.example.

Develop

Pushes to main deploy the showcase to Vercel automatically; the Release workflow publishes @3gs/ui when a Version Packages PR is merged.

pnpm install
pnpm dev          # showcase at http://localhost:5173, consumes the lib from source
pnpm build        # builds the lib (dist/index.js, index.cjs, ui.css, .d.ts) and the site
pnpm typecheck

Adding a component: read packages/ui/CONTRIBUTING.md.

Design export

pnpm design:export regenerates design/ from the CSS — the --gs-* tokens as W3C DTCG JSON (design/tokens/3gs.{dark,light}.tokens.json, for Tokens Studio / variable importers) and two SVG sticker sheets of the gel styles (design/3gs-gel-sheet-{dark,light}.svg, drawn with real gradients so Figma's SVG import yields editable vectors). Import notes in design/README.md.

component-library
css
design-system
ios
iphone
react
retro
skeuomorphism
typescript
ui-components

Contributors

TimonBozzApps/design-system-3gs

The iPhone 3GS / iOS 3 glossy UI as a React component library — 23 components, design tokens, dark + classic light themes. Plus: paste any URL and see that site as a 2009 iPhone app.

TypeScript

0

32 commits

updated Sep 23, 2026

See the code

See what people are saying

SourceMessageScoreDate

I recreated the iPhone 3GS (iOS 3) UI as a React component library (r/reactjs)

23 components: nav bar, tab bar, grouped tables, the toggle switch, the spinning picker, the on-screen keyboard, HUDs, action sheets, a date picker, ... all built on one "gel" recipe: a vertical gradient with a hard highlight split at 50%, a 1px black outline and a light inner rim. Everything is…

1

Sep 23, 2026

README

3GS UI

Live showcase → design-system-3gs.vercel.app · npm: @3gs/ui

A React design system that reproduces the iPhone 3GS / iOS 3 look — glossy "gel" skeuomorphism — recast in dark mode. Vertical gradients with the hard 50 % highlight, 1 px black outlines with an inner light rim, embossed type, pinstripe grouped tables, the blue ON/OFF switch, the black glass tab bar. Icons come from lucide and are tinted through an SVG gradient so they get the same glass treatment.

Paste any URL into the showcase and it re-renders that website as a 2009 iPhone app — navigable, with the site's own content, search and forms. Share one at /p/news.ycombinator.com.

packages/ui     @3gs/ui — the component library (React 18/19, TypeScript, plain CSS tokens)
apps/site       the showcase site (Vite) — every component inside a 320×480 phone frame

Components (v0.1.0)

ComponentWhat it is
ButtonGel push buttons — default / primary (blue) / destructive (red), 3 sizes, block
SwitchThe 94×27 ON/OFF slider with the sliding blue/dark strip and silver knob
TextField, SearchFieldSunken inputs with inline labels, clear button, and the pill search bar
NavigationBar, BarButton44 px black glass top bar, centered title, pointed back button
TabBar, TabBarItem49 px bottom bar with gradient icons, selected state, red badges
List, ListItemGrouped / plain table views: icon tiles, chevrons, detail text, headers, footers
AlertThe deep-blue modal alert with the top gloss and stacked / paired gel buttons
SegmentedControl, SegmentGel bar cut into equal segments, selected one sunken blue
SliderNative range input styled as the volume slider: blue fill, silver knob, end icons
ActionSheetBottom sheet that slides up: silver / red / dark-cancel gel buttons
PickerThe spinning wheel — scroll-snap drums, cylinder shading, glass selection bar
ProgressBar, ActivityIndicatorBlue gel progress bar and the 12-spoke stepped spinner
StatusBarThe 20 px strip: signal bars, 3G / EDGE / Wi-Fi, clock, Bluetooth, gel battery
Toolbar, ToolbarButton, ToolbarSpacer, ToolbarTitleBottom black bar with plain glossy icon buttons and centred status text
PageControlThe dots — tap a half to page, arrow keys, clickable dots
PopoverThe iPad 3.2 dark bubble with an arrow; auto-flips and clamps, portal or contained
HUDTranslucent black square: spinner / progress / check / error / text toast, auto-dismiss
KeyboardDark alert-style QWERTY with letters / numbers / symbols, shift + caps, key popup, delete repeat, value/onChange binding
DatePickerUIDatePicker presets on the wheel: date / time / dateTime, minute intervals, 12/24 h
ModalSheetFull-screen modal that slides up with its own Cancel / title / Done bar
StepperJoined − / + gel control for list rows, hold-to-repeat, wraps, value well
SearchBarUISearchBar: search pill in a bar with a sliding Cancel and scope buttons
NotificationBannerBlack-glass banner dropping from the top: icon tile, action, swipe-up to dismiss
Icon, BadgeFoundation: glossy lucide wrapper and the red gel badge

Use

pnpm add @3gs/ui lucide-react
import "@3gs/ui/styles.css";
import { NavigationBar, BarButton, List, ListItem, Icon } from "@3gs/ui";
import { Gamepad2 } from "lucide-react";

<div className="gs-root">
  <NavigationBar title="Categories" left={<BarButton variant="back">Featured</BarButton>} />
  <List>
    <ListItem iconTile icon={<Icon icon={Gamepad2} />} title="Games" accessory="chevron" onClick={…} />
  </List>
</div>

Wrap your app (or any subtree) in .gs-root to get the font, colours and black background. Tokens are CSS custom properties prefixed --gs- (packages/ui/src/tokens/tokens.css); override them on .gs-root to retint.

Themes

Dark (the default) is the black-glass take. data-theme="light" switches a subtree to the classic iOS 3 look — blue-gray bars, white cells on the pinstripe, black embossed text:

<div className="gs-root" data-theme="light">…</div>

The tab bar, alerts, HUDs and popovers stay dark in both themes, as they did on the device. Under the hood the light theme only redefines tokens (packages/ui/src/tokens/theme-light.css); components read semantic tokens such as --gs-gradient-bar, --gs-gradient-neutral, --gs-text-on-bar and --gs-text-emboss, so a third theme is one more file of overrides. The showcase's sidebar switch flips every phone screen between the two.

Localization and RTL

Every built-in string (Cancel, Done, space, "Page 2 of 5", aria-labels…) comes from GsProvider; explicit props still win:

<GsProvider locale="de-DE" strings={{ cancel: "Abbrechen", done: "Fertig", on: "EIN", off: "AUS" }}>
  <App />
</GsProvider>

DatePicker takes month / weekday names from the provider's locale. Layout uses logical properties throughout, so dir="rtl" on .gs-root mirrors the back button, chevrons, switches, slider fills and badges (the keyboard stays QWERTY). Overlays that portal to document.body (Alert, ActionSheet, HUD, Popover, ModalSheet) inherit the document's direction — pass dir to them or render them contained.

Props reference

The showcase's per-component prop tables are generated from the TypeScript types (pnpm propsapps/site/src/generated/props.json), including JSDoc descriptions and defaults, so they can't drift from the code.

Publishing

Releases use changesets: add one with pnpm changeset, and the Release workflow opens a "Version Packages" PR that publishes @3gs/ui on merge via npm trusted publishing (OIDC — no token; the publisher is TimonBozzApps/design-system-3gs / release.yml). The job runs only while the repository variable ENABLE_NPM_RELEASE is true. Locally: pnpm changeset:version && pnpm release. pnpm pack:check lists exactly what ships.

"Try it on your site"

The showcase can rebuild any public website as a 2009 iPhone app: /api/preview?url=<site> (a Vercel Function in api/) fetches the page with an SSRF-guarded fetcher (private / link-local / metadata addresses rejected on every redirect hop, 8 s timeout, 4 MB read cap), extracts title, icons, OG image, nav links, headings, CTAs and search forms, and maps them to a ScreenSpec (api/_lib/spec.ts) that the site renders with the library. The phone is navigable: tabs and rows fetch that page and push real screens (with a Back button), and every page carries its actual content — paragraphs, bullet lists, prices and stats, FAQ pairs, pull quotes, in-page images and code — rendered as iOS grouped-table cells. Feed pages (Hacker News, changelogs, blog indexes) are detected structurally and become a news-app list: headline plus a "382 points · 101 comments" meta line. Export a PNG, copy the generated JSX, or share /p/<site>: a standalone page with nothing but the phone, its own unfurl card (/api/share injects the meta tags, /api/image serves the site's image). Results are cached (1 h) and rate-limited (12/min/IP). Deep link: ?site=vercel.com. The mapper is a pure function so an AI-designed one can drop in later. Locally, pnpm dev serves the same endpoint through a Vite middleware; node scripts/preview-smoke.mjs runs the positive/negative cases.

Analytics (opt-in)

The showcase can report anonymous usage to PostHog — cookieless, DNT-respected, no session recording. It is off unless VITE_POSTHOG_KEY is set at build time (Vercel → Environment Variables); without it the SDK isn't even bundled. The key is PostHog's public, write-only project token (phc_…), which is designed to ship in browsers; nothing secret is needed or committed — see apps/site/.env.example.

Develop

Pushes to main deploy the showcase to Vercel automatically; the Release workflow publishes @3gs/ui when a Version Packages PR is merged.

pnpm install
pnpm dev          # showcase at http://localhost:5173, consumes the lib from source
pnpm build        # builds the lib (dist/index.js, index.cjs, ui.css, .d.ts) and the site
pnpm typecheck

Adding a component: read packages/ui/CONTRIBUTING.md.

Design export

pnpm design:export regenerates design/ from the CSS — the --gs-* tokens as W3C DTCG JSON (design/tokens/3gs.{dark,light}.tokens.json, for Tokens Studio / variable importers) and two SVG sticker sheets of the gel styles (design/3gs-gel-sheet-{dark,light}.svg, drawn with real gradients so Figma's SVG import yields editable vectors). Import notes in design/README.md.

component-library
css
design-system
ios
iphone
react
retro
skeuomorphism
typescript
ui-components

Contributors

Languages

TypeScript

68.4%

CSS

20.1%

JavaScript

11.2%