# Accessibility (/docs/accessibility)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
- Containers use `role="tablist"`, items use `role="tab"` with `accessibilityState.selected`.
- Every item has a label. Badges are announced ("Search, 3 new").
- Touch targets are at least 44pt on iOS and 48dp on Android, using `hitSlop` when the visual is smaller.
- `reducedMotion` follows the system setting by default (`'system' | 'always' | 'never'`).
- Tested with VoiceOver and TalkBack, at 200% font scale, and in right-to-left layouts.
# Agent skills (/docs/agent-skills)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
A `using-skiddie` skill teaches agents the adapter pattern and the customization layers, so generated code uses
skiddie the way it's meant to be used. Every docs page is also available as Markdown (use **Copy Markdown** at the top
of a page), and the whole site as [`/llms.txt`](/llms.txt) and [`/llms-full.txt`](/llms-full.txt).
# Changelog (/docs/changelog)
## Unreleased [#unreleased]
- 2026-10-22: Glide Tab Bar (`glide-tab-bar`), an icon-only bar whose underline eases to the tapped tab.
- Docs site and catalog.
# CLI reference (/docs/cli)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
| Command | What it does |
|---|---|
| `skiddie init` | Set up `skiddie.json`, copy `core/`, install peer dependencies |
| `skiddie add ` | Add components and their dependencies. Never overwrites without `--overwrite`. Supports `--dry-run` |
| `skiddie add pro/` | Add a Pro component (needs `skiddie login`) |
| `skiddie login` | Save your Pro license key |
| `skiddie list` | Show the catalog and what's installed |
| `skiddie info ` | Description, dependencies, platforms, docs link |
| `skiddie diff ` | Compare your copy with the latest version |
| `skiddie remove ` | Delete a component's files (keeps `core/`) |
## `skiddie.json` [#skiddiejson]
```json
{
"outDir": "components/skiddie",
"alias": "@/components/skiddie",
"haptics": "expo",
"blur": "expo",
"registry": "https://skiddie.binitt.dev"
}
```
## Using the shadcn CLI instead [#using-the-shadcn-cli-instead]
The registry is shadcn-compatible, so free components also install with:
```bash
npx shadcn@latest add https://skiddie.binitt.dev/r/floating-tab-bar.json
```
# Glide Tab Bar (/docs/components/glide-tab-bar)
A floating bottom bar with rounded corners and outline icons. Tap a tab and a short underline eases across to sit
under it: quick to start, soft to stop, no bounce.
No labels by default, so it suits apps with four or five well-known sections (home, search, likes, notifications,
profile).
## Installation [#installation]
The `skiddie` CLI isn't published yet. The commands below are the planned ones and won't work until v0.1.
If you haven't set skiddie up yet, run `init` first ([Installation](/docs/installation)).
### File structure [#file-structure]
Every skiddie component comes in two versions with the same props and motion. Pick one:
- **NativeWind** (default): styled with `className`. Needs NativeWind v4.
- **StyleSheet**: plain React Native `StyleSheet`, no styling library. Add `--stylesheet`.
Only the highlighted files are this component. `core/` is shared by every skiddie component and copied once by
`init`.
### Icons [#icons]
The bar doesn't ship icons: each tab gets an `icon` render function, so any icon set works. The examples use
[Phosphor](https://phosphoricons.com), which matches the thin outline style:
```bash
npx expo install phosphor-react-native react-native-svg
```
Import each icon from its own file. Importing from `'phosphor-react-native'` itself pulls every icon (about 1,500)
into your bundle.
```tsx
import { HouseIcon } from 'phosphor-react-native/src/icons/House';
```
## Usage [#usage]
### With Phosphor icons [#with-phosphor-icons]
```tsx title="app/(tabs)/_layout.tsx"
import { useState } from 'react';
import { BellIcon } from 'phosphor-react-native/src/icons/Bell';
import { HeartIcon } from 'phosphor-react-native/src/icons/Heart';
import { HouseIcon } from 'phosphor-react-native/src/icons/House';
import { MagnifyingGlassIcon } from 'phosphor-react-native/src/icons/MagnifyingGlass';
import { UserIcon } from 'phosphor-react-native/src/icons/User';
import { GlideTabBar } from '@/components/skiddie/glide-tab-bar';
import type { TabItem } from '@/components/skiddie/core/types';
const items: TabItem[] = [
{ key: 'home', label: 'Home', icon: ({ color, size }) => },
{ key: 'search', label: 'Search', icon: ({ color, size }) => },
{ key: 'likes', label: 'Likes', icon: ({ color, size }) => },
{
key: 'alerts',
label: 'Notifications',
badge: true,
icon: ({ color, size }) => ,
},
{ key: 'me', label: 'Profile', icon: ({ color, size }) => },
];
export function Bar() {
const [tab, setTab] = useState('home');
return ;
}
```
The `icon` function gets `focused`, so you can switch weights for the active tab:
```tsx
icon: ({ focused, color, size }) =>
```
## Props [#props]
Everything from the [shared tab bar contract](/docs/customization) (`items`, `activeKey`, `onChange`,
`onLongPress`, `className` and `classNames` (NativeWind) or `styles` (StyleSheet), `style`, `renderItem`, `animation`, `reducedMotion`, `safeArea`,
`insets`, `testID`), plus:
| Prop | Type | Default | What it does |
|---|---|---|---|
| `showLabels` | `boolean` | `false` | Show each label under its icon. Screen readers read labels either way. |
| `iconSize` | `number` | `24` | Size passed to every `icon` function. |
The `haptics` prop is accepted but does nothing yet; it starts working when the shared haptics adapter lands.
## Customization [#customization]
In order of power:
1. **Tokens.** The underline uses `indicator`, active icons `active`, the rest `inactive`, the bar
`background` and `border` (`core/tokens.ts`). Change them there and every skiddie component follows.
2. **Classes per part** with `classNames`. Your classes are merged last, so they win:
```tsx
```
Slots: `container`, `bar`, `item`, `itemActive`, `icon`, `label`, `labelActive`, `indicator`, `badge`,
`badgeText`.
**StyleSheet version:** the same slots take style objects through `styles`:
```tsx
```
3. **`renderItem`** to draw each tab yourself. The underline keeps working, because it only needs the tab count.
4. **`animation`** for the motion:
```tsx
// snappier ease-out
// springy, overshoots a little
// bouncier
```
## How it works [#how-it-works]
Every tab is the same width (`flex-1`). The bar measures its row once (`onLayout`) and keeps the active tab's
**index** in a Reanimated shared value. On the UI thread, the underline's `translateX` is `index × tab width`, so a
tap starts a new ease-out from wherever the line is now, even mid-animation, and rotating the device just re-measures
without a jump. React only re-renders when the active tab changes, never per frame.
## Accessibility [#accessibility]
- The bar is a `tablist`; each tab is a `tab` with `selected` and `disabled` state.
- Labels are read even when hidden, together with the selected state.
- Badges are part of the label: a count reads "Search, 3", a dot reads "Notifications, new".
- Tabs are at least 44pt tall and share the full bar width.
- With **Reduce Motion** on (iOS) or **Remove animations** (Android), the underline moves without animating. Force
it with `reducedMotion="always"` or `"never"`.
- RTL: tabs and the underline mirror automatically.
## Platform support [#platform-support]
Works in Expo Go: it only needs Reanimated and safe-area-context, which Expo Go includes.
# Customization model (/docs/customization)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
| Layer | Use it for |
|---|---|
| 1. **Tokens** | Colors, radius, spacing, motion for everything |
| 2. **Variants** | Presets like `variant="pill"`, `indicator="underline"`, `labelMode="active"` |
| 3. **`className` / `classNames` slots** | Restyle with Tailwind classes. `className` for the root, `classNames` for parts: `bar`, `item`, `itemActive`, `indicator`, `label`, `labelActive`, `badge` |
| 4. **Render props** | Replace parts: `renderItem`, `renderIndicator`, `renderBadge` |
| 5. **Motion** | `animation={{ type: 'spring', spring: {{ damping, stiffness }} }}`, `reducedMotion` |
| 6. **Headless hooks** | Build your own on the same mechanics: `useTabBar`, `useSegmented`, `useCollapsingHeader` |
| 7. **The source** | It's in your project. Change anything. `skiddie diff` shows upstream changes later |
## Classes [#classes]
Your classes are merged after the defaults with `tailwind-merge`, so they win:
```tsx
```
`style` still works as an escape hatch and beats classes.
# Expo Go vs dev builds (/docs/expo-go)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
Most components only use Reanimated, Gesture Handler and SVG, which ship in Expo Go. Some features need native code
that Expo Go doesn't include. True iOS Liquid Glass, for example, needs an app built against the right iOS SDK. Those
components fall back gracefully in Expo Go and say so on their page.
## Making a development build [#making-a-development-build]
```bash
npx expo install expo-dev-client
npx expo run:ios # or: npx expo run:android
```
Or build in the cloud with EAS. Each component page has a **Platform support** table with an Expo Go row.
# Introduction (/docs)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
skiddie is a set of **navigation components** for React Native and Expo: tab bars, top tabs, headers, drawers,
menus and indicators. You don't install a UI package. The CLI copies the component source into your project, so you can
read it, change it and ship it.
## Principles [#principles]
- **Smooth by default.** Every animation runs on the UI thread with Reanimated.
- **Styled with NativeWind.** Components use Tailwind classes (NativeWind v4), so you restyle them with the classes
you already write.
- **Expo-first, not Expo-only.** Expo extras like haptics and blur sit behind small adapters you can swap.
- **Navigator-agnostic.** Use a component on its own, or as the `tabBar` of Expo Router or React Navigation through one
adapter.
- **Accessible.** Proper roles and selected states, labelled items, announced badges and 44pt touch targets.
- **Yours to change.** Tokens, `className` slots, render props, headless hooks, and finally the source itself.
# Installation (/docs/installation)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
## Requirements [#requirements]
- Expo SDK 57+ (React Native 0.86+). Older SDKs will likely work but are not tested yet.
- **NativeWind v4** with Tailwind CSS 3.4. If you don't have it yet, `skiddie init` offers to set it up. NativeWind v5
isn't supported yet.
## 1. Initialize [#1-initialize]
```bash
npx skiddie@latest init
```
```bash
pnpm dlx skiddie@latest init
```
```bash
yarn dlx skiddie@latest init
```
```bash
bunx skiddie@latest init
```
`init` detects Expo or bare React Native and your package manager, asks where components should go (default
`@/components/skiddie`) and which haptics and blur adapters you want, then:
- writes `skiddie.json`,
- copies the shared `core/` folder (tokens, Tailwind preset, `cn`, theme, adapters, accessibility helpers),
- adds the skiddie preset and folder to your `tailwind.config.js` (see below),
- installs the peer dependencies with `npx expo install` on Expo, or your package manager otherwise.
## 2. Peer dependencies [#2-peer-dependencies]
| Package | Why |
|---|---|
| `react-native-reanimated` + `react-native-worklets` | UI-thread animation |
| `react-native-gesture-handler` | Gestures |
| `react-native-safe-area-context` | Safe areas |
| `react-native-svg` | Shapes and indicators |
| `nativewind` (v4) + `tailwindcss` (3.4) | Styling |
| `clsx` + `tailwind-merge` (v2) | Merging your classes with the defaults |
Some components add optional ones (`expo-haptics`, `expo-blur`, `expo-glass-effect`). Their pages list them.
## 3. Tailwind config [#3-tailwind-config]
`init` makes these two changes. If you set things up by hand, make sure both are there, or the components render
unstyled:
```js title="tailwind.config.js"
module.exports = {
content: [
'./app/**/*.{js,jsx,ts,tsx}',
'./components/skiddie/**/*.{js,jsx,ts,tsx}', // [!code highlight]
],
presets: [
require('nativewind/preset'),
require('./components/skiddie/core/tailwind-preset'), // [!code highlight]
],
};
```
Add `react-native-css-interop` as a direct dependency, at the same version `nativewind` uses. NativeWind's Babel
transform imports it from your app code, and pnpm only exposes direct dependencies.
## 4. Add a component [#4-add-a-component]
```bash
npx skiddie@latest add floating-tab-bar
```
# License (/docs/license)
These terms are a draft and will be finalized before the first release.
## Free components [#free-components]
Free to use, modify and ship inside any app you build, personal or commercial, including client work. No fee and no
attribution needed.
You **can't** redistribute free components as a component library, UI kit, template, starter kit or code generator,
publish their source on its own, or use them to build a competing product.
## Pro components [#pro-components]
Same rights inside your apps, for license holders. Per developer. No redistribution, resale or publishing of the source.
## FAQ [#faq]
**Can I use free components in a commercial app?** Yes.
**Can my app's repository be public?** Yes, if skiddie code is part of a real app. Publishing the components on their
own isn't allowed.
**Can I use them for client work?** Yes.
# MCP server (/docs/mcp)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
`@skiddie/mcp` gives coding agents three tools: `list_components`, `get_component` (meta, usage and install command)
and `get_source` (free components only).
```json title="mcp.json"
{
"mcpServers": {
"skiddie": { "command": "npx", "args": ["-y", "@skiddie/mcp"] }
}
}
```
# Navigator adapters (/docs/navigator-adapters)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
Components take their state through props (`items`, `activeKey`, `onChange`). One adapter turns any of them into a
React Navigation `tabBar`, which Expo Router's `` uses too.
```tsx title="app/(tabs)/_layout.tsx"
import { Tabs } from 'expo-router';
import { FloatingTabBar } from '@/components/skiddie/floating-tab-bar';
import { createTabBarAdapter } from '@/components/skiddie/core/adapters/react-navigation';
const TabBar = createTabBarAdapter(FloatingTabBar, { haptics: 'selection' });
export default function TabLayout() {
return (
} screenOptions={{ headerShown: false }}>
);
}
```
The adapter maps each route's `title`/`tabBarLabel`, `tabBarIcon`, `tabBarBadge`, accessibility label and test ID,
emits `tabPress` / `tabLongPress`, and respects `preventDefault()`.
## Standalone [#standalone]
```tsx
const [tab, setTab] = useState('home');
;
```
# Pro & licensing (/docs/pro)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
Pro components are installed with the same CLI, using your license key.
```bash
npx skiddie@latest login # paste your license key once
npx skiddie@latest add pro/glass-tab-bar
```
In CI, set `SKIDDIE_LICENSE_KEY` instead of running `login`.
- The code is copied into your project like any free component, and it's yours to change.
- One license per developer. Use it in unlimited personal and client apps.
- You can't redistribute or resell Pro components, or publish their source.
Pricing and checkout open with the first Pro release.
# Theming & tokens (/docs/theming)
skiddie is being built. Commands and APIs on this page are the planned design and may change before v0.1.
Every value lives in `components/skiddie/core/tokens.ts`. The skiddie Tailwind preset turns those tokens into
classes, so components use `bg-sk-background`, `text-sk-active`, `rounded-sk-bar` and so on.
```ts title="core/tokens.ts (excerpt)"
export const tokens = {
colors: {
light: { background: '#FFFFFF', active: '#0A0A0A', inactive: '#A1A1AA', indicator: '#0A0A0A', badge: '#EF4444' },
dark: { background: '#0A0A0A', active: '#FAFAFA', inactive: '#71717A', indicator: '#FAFAFA', badge: '#EF4444' },
},
radius: { bar: 28, item: 20, indicator: 999 },
motion: { spring: { damping: 18, stiffness: 220, mass: 1 }, durationMs: 220 },
} as const;
```
## Token classes [#token-classes]
| Token | Classes |
|---|---|
| Colors | `bg-sk-background`, `bg-sk-surface`, `text-sk-active`, `text-sk-inactive`, `bg-sk-indicator`, `bg-sk-badge`, `text-sk-badge-text`, `border-sk-border` (any color utility works: `text-`, `bg-`, `border-`…, with `/50` opacity) |
| Radius | `rounded-sk-bar`, `rounded-sk-item`, `rounded-sk-indicator` |
| Spacing | `px-sk-bar-x`, `py-sk-bar-y`, `gap-sk-gap`, `inset-x-sk-inset`, `min-h-sk-touch` |
| Type | `text-sk-label` |
Colors are CSS variables, so light and dark switch on their own. System appearance works out of the box, and so do
apps that toggle `dark` manually with `darkMode: 'class'`.
## Three ways to theme [#three-ways-to-theme]
1. **Edit `tokens.ts`** for app-wide defaults, then restart Metro (`expo start -c`).
2. **Override variables for a subtree** with NativeWind's `vars()`:
```tsx
import { vars } from 'nativewind';
```
3. **Classes on one instance**, through `className` and `classNames`. See [Customization](/docs/customization).