Prototype UI
Docs
Introduction

Introduction

A UI component generation solution that starts from the essence of interaction

What is this

This project is named Prototype UI, a component generation solution based on Prototype.

Currently in its early stages, it appears to be a Web Component or pure JavaScript version of Radix UI. Additionally, the documentation site and component styles are inspired by shadcn/ui.

Project Status

🚧 This project is currently in early development stage.

What's Available

  • Basic Web Component implementation
  • Documentation site (you're looking at it)
  • Core prototype system

What's Coming

  • CLI tool for component installation
  • More component implementations
  • More theme options
  • Framework adapters (React, Vue, etc.)

Why we do this

The design philosophy of Prototype UI is Adapter(Prototype) => Component

We have used too many technologies to build interfaces, such as React, Vue, Svelte, Solid for the web, native Flutter, Qt, etc. Every time we write UI, we always start with components like Button. We've written Button too many times, yet their interactive essence remains the same.

Prototype UI hopes to find a balance between interaction and development, seeking a solution that is adaptable enough for all platforms while starting from the essence of interaction.

When the next operating system emerges, we won't need to develop a new set of UI libraries for each design language; when the next design language appears, we won't need to struggle to adapt it to all technical solutions.

Perhaps when the next interaction system appears, we can have a more reasonable starting point, rather than rewriting Button over and over again.

Get Involved

We're in the early stages of development and would love your input!

How to Contribute

  • Open issues to discuss ideas or report bugs
  • Submit pull requests for improvements
  • Share your thoughts in discussions

Development Roadmap

  1. Phase One: Web Platform
  • Complete core prototype system
  • Implement basic components
  • Build CLI tool
  • Improve documentation

  1. Phase Two: Cross Platform
  • Add framework adapters
  • Support more platforms
  • Expand design language themes

Current Focus

  • Stabilizing the core prototype system
  • Implementing basic components
  • Building the documentation site

Docs
Quick Start

Quick Start

To use it, to contribute, whatever the purpose, we need to get it running first, which is fortunately very simple

Installation and Running

Since there is no CLI tool to help you install yet, we recommend cloning the project source code and running it locally

git clone https://github.com/guangliang2019/Prototype-UI.git

Make sure you have Node.js 18 or higher installed, then run the following commands in the directory containing package.json

npm install

npm run dev

If successful, you will be able to see the Prototype UI documentation site at http://localhost:5173/

Preparation Before Use

If you want to use the components provided by Prototype UI, such as Web Component style Headless Button or Shadcn theme Button

You can directly copy the prototype and shadcn directories from the project's src/components directory to your project. Additionally, you need to copy the src/core directory to your project

src/core/adapters includes various types of adapters. It is recommended to delete adapters that your project does not use. For example, if you only use Web Component style components, then you only need to keep the src/core/adapters/web/@web-component directory

If you don't delete unnecessary adapters, it may cause your project size to increase (adapters have no dependencies between them, it's recommended to decide which to keep based on your project)

In the future, we will provide a CLI tool to help you write components directly into your project, but for now, you'll have to handle it manually

Prototype and Component

The design philosophy of Prototype UI is Adapter(Prototype) => Component, so you'll see code like this in the src/components directory

export const PrototypeButtonPrototype = definePrototype({
  name: 'prototype-button',
  setup: asButton,
});

export const PrototypeButton = WebComponentAdapter(PrototypeButtonPrototype);

In the above example, we define a prototype PrototypeButtonPrototype, then use WebComponentAdapter to adapt it into a Web Component PrototypeButton

In the setup function of the prototype PrototypeButtonPrototype, we use the asButton function, which adapts the prototype to button behavior

We will explain the setup function and asButton function in detail later

The final output PrototypeButton component has APIs and behavior consistent with native Web Components. It can be inherited, extended, and any Web Component features can be used

The components in the current documentation site are developed based on prototypes-generated Web Components, rather than using the prototype and adapter pattern from start to finish, because we need to ensure that the output components are truly consistent with the developer experience of native components

setup Function

setup is the exact description of prototype behavior, representing the common logic part of implementing the same interactive component across any platform

The asButton used earlier is one of the default setup behaviors provided by Prototype UI, using them is not mandatory

The setup function has one parameter, usually called p, which is the "remote control" of the current prototype. What you need to do is use it in the setup function to describe the behavior of the current prototype

For example, a custom button that prints "Hello Prototype UI" to the console when clicked

const MyButtonPrototype = definePrototype({
  name: 'my-button',
  setup: (p) => {
    p.event.on('click', () => {
      p.debug.log('Hello Prototype UI')
    })
  },
});

In the above code, we use p.event.on to listen for click events and p.debug.log to print logs

p has many capabilities, which we will gradually explore

More About p's Capabilities

p's functionality is divided into several major parts. This section might contain a lot of information. If it's difficult to understand at first, you can skip it and come back to consult when needed

  • p.event Used to listen for interaction events, the only way for components to interact with end users
  • p.debug Used for printing logs, recording performance, enabling debugging features, etc. This feature is very useful during development

  • p.props Used to set, modify, and listen to component property parameters, such as button style types (primary button, secondary button, border button, ghost button, danger button)
  • p.state Used to set and listen to component states. Here, states refer to states similar to animation state machines, such as button disabled state, active state, focus state, etc.
  • p.context Used to provide, listen to, and obtain component context. Context refers to data shared between different parts of a component
  • p.lifecycle Used to execute custom logic at specified component lifecycle stages

  • p.view's functionality is divided into two categories, mainly related to rendering scheduling and elements
  • Rendering scheduling related, such as requesting view updates, forcing view updates
  • Element related, such as obtaining runtime rendering elements, comparing element positions, obtaining specific element parameters, etc.

Let's take an example, such as a Tabs component, which consists of TabsTrigger and TabsContent two parts

The logic is that when TabsTrigger is clicked, add the data-selected attribute to the TabsContent that carries the same value as TabsTrigger, making it convenient for subsequent custom styling

const MyTabsContext = createContext<{value: string; changeTab: (value: string) => void}>('my-tabs')

const MyTabsPrototype = definePrototype({
 
  name: 'my-tabs',
  setup: (p) => {
    p.props.define({
      defaultValue: ""
    })

    p.context.provide(MyTabsContext, (updateContext) => {
      const props = p.props.get()
      
      return {
        value: props.defaultValue,
        changeTab: (value: string) => {
          updateContext({
            value
          })
        }
      }
    })
  },
});

const MyTabsTriggerPrototype = definePrototype({
  name: 'my-tabs-trigger',
  setup: (p) => {
    p.props.define({
      value: ""
    })
    const selected = p.state.define(false, 'data-selected')
    
    p.context.watch(MyTabsContext, (context, changedKeys) => {
      if (changedKeys.includes('value')) {
        selected.set(context.value === p.props.get().value)
      }
    })

    p.event.on('click', () => {
      const context = p.context.get(MyTabsContext)
      const props = p.props.get()
      context.changeTab(props.value)
    })
  }
});

const MyTabsContentPrototype = definePrototype({
  name: 'my-tabs-content',
  setup: (p) => {
    p.props.define({
      value: ""
    })
    const selected = p.state.define(false, 'data-selected')
    p.context.watch(MyTabsContext, (context, changedKeys) => {
      if (changedKeys.includes('value')) {
        selected.set(context.value === p.props.get().value)
      }
    })
  }
});

Return Value of setup

In the previous examples, we mainly demonstrated the usage of p in the setup function body. Actually, the return value of the setup function is also very important

The return value of the setup function is an object with 4 components, all of which are optional, with render and expose being more commonly used

  • render The component's rendering template, default is to use the current component as a slot. Using custom rendering functions can control the rendering structure
  • expose APIs exposed by the current component to the outside, these APIs can be easily used externally
  • states If the current setup function is used as a hook function, which internal states can be controlled by external components
  • actions If the current setup function is used as a hook function, which internal states can be controlled by external components

const MyTabsContext = createContext<{value: string; changeTab: (value: string) => void}>('my-tabs')

const MyTabsPrototype = definePrototype({
  name: 'my-tabs',
  setup: (p) => {
    p.props.define({
      defaultValue: ""
    })

    p.context.provide(MyTabsContext, (updateContext) => {
      const props = p.props.get()
      return {
        value: props.defaultValue,
        changeTab: (value: string) => {
          updateContext({
            value
          })
        }
      }
    })

    return {
      exposes: {
        changeTab: (value: string) => {
          p.context.get(MyTabsContext).changeTab(value)
        }
      }
    }
  },
});

After adding exposes, we can use the changeTab function to control the component's internal state from outside the component

Taking Web Component as an example, you can directly get the Tabs root component's DOM element, then directly call the changeTab method to control Tabs switching

Like this:

const tabs = document.querySelector('my-tabs')
tabs.changeTab('tab-1')

Different Adapters handle exposes differently, but they all try to keep the usage experience close to the target technical solution

Shadcn
Button

Button

Displays a button or a component that looks like a button.

PreviewCode
Select Theme
Copy as...
Button
<shadcn-button variant="primary">Button</shadcn-button>

Examples

Primary

PreviewCode
Select Theme
Copy as...
Primary
<shadcn-button variant="primary">Primary</shadcn-button>

Secondary

PreviewCode
Select Theme
Copy as...
Secondary
<shadcn-button>Secondary</shadcn-button>

Outline

PreviewCode
Select Theme
Copy as...
Outline
<shadcn-button variant="outline">Outline</shadcn-button>

Ghost

PreviewCode
Select Theme
Copy as...
Ghost
<shadcn-button variant="ghost">Ghost</shadcn-button>
PreviewCode
Select Theme
Copy as...
Link
<shadcn-button variant="link">Link</shadcn-button>

Destructive

PreviewCode
Select Theme
Copy as...
Destructive
<shadcn-button variant="destructive">Destructive</shadcn-button>
Shadcn
Card

Card

Card are used to organize content on the page.

PreviewCode
Select Theme
Copy as...

Create project

Deploy your new project in one-click

Framework

Option 1Option 2Option 3
CancleDeploy
Code component is in development
Shadcn
Tabs

Tabs

Tabs are used to organize content on the page.

PreviewCode
Select Theme
Copy as...
AccountPassword

Account

Make changes to your account here. Click save when you're done.

Password

Change your password here. After saving, you'll be logged out.

Code component is in development
Shadcn
Select

Select

Displays a select or a component that looks like a select.

PreviewCode
Select Theme
Copy as...
Option 1Option 2Option 3
Code component is in development
Shadcn
Input

Input

Displays a select or a component that looks like a select.

PreviewCode
Select Theme
Copy as...
Code component is in development
Shadcn
Scroll Area

Scroll Area

Scroll Area

PreviewCode
Select Theme
Copy as...
1
2
3
4
5
6
7
8
Code component is in development
Shadcn
Switch

Switch

A switch component.

PreviewCode
Select Theme
Copy as...
<shadcn-switch></shadcn-switch>
Prototype
Transition

Transition

Transition is used to open or hide content.

PreviewCode
Select Theme
Copy as...
Toggle Transition
<prototype-transition id="transition" class="opacity-[0.95] size-[6.25rem] block rounded-xl bg-primary shadow-lg transition duration-[400ms] data-[closed]:scale-50 data-[closed]:rotate-[-120deg] data-[closed]:opacity-0 data-[leave]:duration-[200ms] data-[leave]:ease-in-out data-[leave]:data-[closed]:scale-95 data-[leave]:data-[closed]:rotate-[0deg]"></prototype-transition><shadcn-button id="button" class="mt-10">Toggle Transition</shadcn-button><script>  const button = document.getElementById('button');  const transition = document.getElementById('transition');  button?.onClick = () => {    transition.open = !transition.open;  }</script>

Overlay test

PreviewCode
Select Theme
Copy as...
Toggle Overlay
<shadcn-button tabindex="0" class="select-none whitespace-nowrap inline-flex items-center justify-center gap-2 rounded-md h-9 px-4 py-2 cursor-pointer text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring shadow-sm bg-secondary text-secondary-foreground  hover:bg-secondary/80" data-active="">  Toggle Overlay</shadcn-button><prototype-overlay style="position: relative; width: 0px; height: 0px;"></prototype-overlay>

Motion Scroll Test

PreviewCode
Select Theme
Copy as...
1
2
3
4
5
Prototype
Select

Select

Displays a list of options for the user to pick from—triggered by a button.

PreviewCode
Select Theme
Copy as...
Selected: Option 1Option 2Option 3Option 4Option 5
Code component is in development
Prototype
Scroll Area

Scroll Area

Scroll Area

PreviewCode
Select Theme
Copy as...
1
2
3
4
5
6
7
8
Code component is in development
Prototype
Radio Group

Radio Group

Displays a list of options for the user to pick from—triggered by a button.

PreviewCode
Select Theme
Copy as...
Button
Code component is in development
Prototype
Resizable

Resizable

Accessible resizable panel groups and layouts with keyboard support.

PreviewCode
Select Theme
Copy as...
OneTwoThree
Code component is in development
Prototype
Button

Button

Describe Button

PreviewCode
Select Theme
Copy as...
Hello, Button!
Code component is in development
Prototype
TestTab

TestTab

Describe TestTab

PreviewCode
Select Theme
Copy as...
Tab 1Tab 2Tab 3Tab 4
Code component is in development
Prototype
TestButton

TestButton

Describe TestButton

PreviewCode
Select Theme
Copy as...
Hello, Test-button!
Click me
Code component is in development
Prototype
Switch

Switch 开关

开关组件允许用户在两种状态之间切换

基础用法

基本介绍

Switch组件提供了一个简单的接口来表示开关状态

基础用法

PreviewCode
Select Theme
Copy as...

基础用法

Prototype
Checkbox

Checkbox

复选框组件

PreviewCode
Select Theme
Copy as...
components

Check out some examples

Dashboard, cards, authentication. Some examples built using the components.
Use this as a guide to build your own.

Get startedComponents