diff --git a/examples/button.tsx b/examples/button.tsx
index a72479e..f664fa9 100644
--- a/examples/button.tsx
+++ b/examples/button.tsx
@@ -23,12 +23,12 @@ const Button = define('Button', {
states: {
":not(:disabled):hover": {
- transform: 'translateY(-2px) !important',
+ transform: 'translateY(-2px)',
filter: 'brightness(1.05)'
},
":not(:disabled):active": {
- transform: 'translateY(1px) !important',
- boxShadow: '0 2px 3px rgba(0, 0, 0, 0.2) !important'
+ transform: 'translateY(1px)',
+ boxShadow: '0 2px 3px rgba(0, 0, 0, 0.2)'
},
},
diff --git a/examples/form.tsx b/examples/form.tsx
new file mode 100644
index 0000000..e5c0370
--- /dev/null
+++ b/examples/form.tsx
@@ -0,0 +1,199 @@
+import { define } from '../src'
+import { ExampleSection } from './ssr/helpers'
+
+const Input = define('Input', {
+ base: 'input',
+
+ padding: '12px 16px',
+ fontSize: 16,
+ border: '2px solid #e5e7eb',
+ borderRadius: 8,
+ background: 'white',
+ color: '#111827',
+ transition: 'all 0.2s ease',
+ width: '100%',
+ boxSizing: 'border-box',
+
+ states: {
+ ':focus': {
+ outline: 'none',
+ borderColor: '#3b82f6',
+ boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)'
+ },
+ ':disabled': {
+ background: '#f3f4f6',
+ color: '#9ca3af',
+ cursor: 'not-allowed'
+ }
+ },
+
+ variants: {
+ status: {
+ error: {
+ borderColor: '#ef4444',
+ states: {
+ ':focus': {
+ borderColor: '#ef4444',
+ boxShadow: '0 0 0 3px rgba(239, 68, 68, 0.1)'
+ }
+ }
+ },
+ success: {
+ borderColor: '#10b981',
+ states: {
+ ':focus': {
+ borderColor: '#10b981',
+ boxShadow: '0 0 0 3px rgba(16, 185, 129, 0.1)'
+ }
+ }
+ }
+ }
+ }
+})
+
+const Textarea = define('Textarea', {
+ base: 'textarea',
+
+ padding: '12px 16px',
+ fontSize: 16,
+ border: '2px solid #e5e7eb',
+ borderRadius: 8,
+ background: 'white',
+ color: '#111827',
+ transition: 'all 0.2s ease',
+ width: '100%',
+ minHeight: 120,
+ boxSizing: 'border-box',
+ fontFamily: 'inherit',
+ resize: 'vertical',
+
+ states: {
+ ':focus': {
+ outline: 'none',
+ borderColor: '#3b82f6',
+ boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)'
+ }
+ }
+})
+
+const FormGroup = define('FormGroup', {
+ marginBottom: 24,
+
+ parts: {
+ Label: {
+ base: 'label',
+ display: 'block',
+ fontSize: 14,
+ fontWeight: 600,
+ color: '#374151',
+ marginBottom: 8
+ },
+ Helper: {
+ fontSize: 13,
+ color: '#6b7280',
+ marginTop: 6
+ },
+ Error: {
+ fontSize: 13,
+ color: '#ef4444',
+ marginTop: 6
+ }
+ },
+
+ render({ props, parts: { Root, Label, Helper, Error } }) {
+ return (
+
+ {props.label && }
+ {props.children}
+ {props.helper && {props.helper}}
+ {props.error && {props.error}}
+
+ )
+ }
+})
+
+const Checkbox = define('Checkbox', {
+ parts: {
+ Input: {
+ base: 'input[type=checkbox]',
+ width: 20,
+ height: 20,
+ cursor: 'pointer'
+ },
+ Label: {
+ base: 'label',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 12,
+ cursor: 'pointer',
+ fontSize: 16,
+ color: '#374151',
+
+ states: {
+ ':hover': {
+ color: '#111827'
+ }
+ },
+
+ selectors: {
+ '@Input:disabled + &': {
+ cursor: 'not-allowed',
+ color: '#9ca3af'
+ }
+ }
+ }
+ },
+
+ render({ props, parts: { Root, Input, Label } }) {
+ return (
+
+
+
+ )
+ }
+})
+
+export const FormExamplesContent = () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+)
diff --git a/examples/spa/app.tsx b/examples/spa/app.tsx
index f4a659a..de80426 100644
--- a/examples/spa/app.tsx
+++ b/examples/spa/app.tsx
@@ -2,6 +2,7 @@ import { define } from '../../src'
import { ButtonExamplesContent } from '../button'
import { ProfileExamplesContent } from '../profile'
import { NavigationExamplesContent } from '../navigation'
+import { FormExamplesContent } from '../form'
export const Main = define('SpaMain', {
base: 'div',
@@ -146,6 +147,11 @@ const HomePage = () => (
title="Navigation"
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
/>
+
+
>
)
@@ -153,6 +159,7 @@ const HomePage = () => (
const ProfilePage = () =>
const ButtonsPage = () =>
const NavigationPage = () =>
+const FormPage = () =>
export function route(path: string) {
switch (path) {
@@ -165,6 +172,8 @@ export function route(path: string) {
return
case '/spa/navigation':
return
+ case '/spa/form':
+ return
default:
return
404 Not Found
}
@@ -182,6 +191,7 @@ export function App() {
Profile
Buttons
Navigation
+ Forms
{route(path)}
diff --git a/examples/ssr/helpers.tsx b/examples/ssr/helpers.tsx
index 808736a..b7d8cf6 100644
--- a/examples/ssr/helpers.tsx
+++ b/examples/ssr/helpers.tsx
@@ -97,6 +97,7 @@ export const Layout = define({
Profile
Buttons
Navigation
+ Forms
{props.children}
diff --git a/examples/ssr/pages.tsx b/examples/ssr/pages.tsx
index 2483ee0..bf3b175 100644
--- a/examples/ssr/pages.tsx
+++ b/examples/ssr/pages.tsx
@@ -3,6 +3,7 @@ import { Layout } from './helpers'
import { ButtonExamplesContent } from '../button'
import { ProfileExamplesContent } from '../profile'
import { NavigationExamplesContent } from '../navigation'
+import { FormExamplesContent } from '../form'
const P = define('SSR_P', {
color: '#6b7280',
@@ -76,6 +77,11 @@ export const IndexPage = ({ path }: any) => (
title="Navigation"
desc="Navigation patterns including tabs, pills, vertical nav, and breadcrumbs"
/>
+
+
)
@@ -97,3 +103,9 @@ export const NavigationExamplesPage = ({ path }: any) => (
)
+
+export const FormExamplesPage = ({ path }: any) => (
+
+
+
+)
diff --git a/server.tsx b/server.tsx
index 1d9f6a2..d6b29fc 100644
--- a/server.tsx
+++ b/server.tsx
@@ -1,5 +1,5 @@
import { Hono } from 'hono'
-import { IndexPage, ProfileExamplesPage, ButtonExamplesPage, NavigationExamplesPage } from './examples/ssr/pages'
+import { IndexPage, ProfileExamplesPage, ButtonExamplesPage, NavigationExamplesPage, FormExamplesPage } from './examples/ssr/pages'
import { LandingPage } from './examples/ssr/landing'
import { styles, stylesToCSS } from './src'
@@ -15,6 +15,8 @@ app.get('/ssr/buttons', c => c.html())
app.get('/ssr/navigation', c => c.html())
+app.get('/ssr/form', c => c.html())
+
app.get('/styles', c => c.text(stylesToCSS(styles)))
app.get('/spa/*', async c => c.html(await Bun.file('./examples/spa/index.html').text()))
diff --git a/src/types.ts b/src/types.ts
index a68dc9a..6f8b817 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -6,69 +6,136 @@ export type TagDef = {
parts?: Record
variants?: Record>
render?: (obj: any) => any
-
- // layout-related
alignContent?: 'normal' | 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch' | 'start' | 'end' | 'baseline'
alignItems?: 'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' | 'start' | 'end' | 'self-start' | 'self-end'
alignSelf?: 'auto' | 'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' | 'start' | 'end' | 'self-start' | 'self-end'
aspectRatio?: number | string
+
bottom?: number | string
+ left?: number | string
+ right?: number | string
+ top?: number | string
+ inset?: number | string
+
+ // logical positioning / sizing
+ insetBlock?: number | string
+ insetInline?: number | string
+ insetBlockStart?: number | string
+ insetBlockEnd?: number | string
+ insetInlineStart?: number | string
+ insetInlineEnd?: number | string
+
boxSizing?: 'content-box' | 'border-box'
+
columnGap?: number | string
+ rowGap?: number | string
+ gap?: number | string
+
contain?: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'
+
display?: 'block' | 'inline' | 'inline-block' | 'flex' | 'inline-flex' | 'grid' | 'inline-grid' | 'flow-root' | 'none' | 'contents' | 'table' | 'table-row' | 'table-cell'
+
+ // float layout
+ float?: 'left' | 'right' | 'inline-start' | 'inline-end' | 'none'
+ clear?: 'left' | 'right' | 'both' | 'inline-start' | 'inline-end' | 'none'
+
flex?: number | string
flexBasis?: number | string
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'
flexGrow?: number
flexShrink?: number
flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse'
- gap?: number | string
+ flexFlow?: string
+
gridAutoFlow?: 'row' | 'column' | 'dense' | 'row dense' | 'column dense'
+ gridAutoColumns?: string
+ gridAutoRows?: string
gridColumn?: string
- gridGap?: number | string
+ gridColumnStart?: string | number
+ gridColumnEnd?: string | number
gridRow?: string
+ gridRowStart?: string | number
+ gridRowEnd?: string | number
+ gridArea?: string
+ gridGap?: number | string
gridTemplateColumns?: string
gridTemplateRows?: string
+ gridTemplateAreas?: string
+
height?: number | string
- inset?: number | string
- justifyContent?: 'normal' | 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'start' | 'end' | 'left' | 'right' | 'stretch'
- justifyItems?: 'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' | 'start' | 'end' | 'self-start' | 'self-end' | 'left' | 'right'
- justifySelf?: 'auto' | 'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' | 'start' | 'end' | 'self-start' | 'self-end' | 'left' | 'right'
- left?: number | string
+ width?: number | string
+ maxHeight?: number | string
+ maxWidth?: number | string
+ minHeight?: number | string
+ minWidth?: number | string
+
+ // logical sizes
+ blockSize?: number | string
+ inlineSize?: number | string
+ minBlockSize?: number | string
+ maxBlockSize?: number | string
+ minInlineSize?: number | string
+ maxInlineSize?: number | string
+
margin?: number | string
marginBottom?: number | string
marginLeft?: number | string
marginRight?: number | string
marginTop?: number | string
- maxHeight?: number | string
- maxWidth?: number | string
- minHeight?: number | string
- minWidth?: number | string
- order?: number
- overflow?: 'visible' | 'hidden' | 'scroll' | 'auto' | 'clip'
- overflowX?: 'visible' | 'hidden' | 'scroll' | 'auto' | 'clip'
- overflowY?: 'visible' | 'hidden' | 'scroll' | 'auto' | 'clip'
+
padding?: number | string
paddingBottom?: number | string
paddingLeft?: number | string
paddingRight?: number | string
paddingTop?: number | string
+
+ order?: number
+
+ overflow?: 'visible' | 'hidden' | 'scroll' | 'auto' | 'clip'
+ overflowX?: 'visible' | 'hidden' | 'scroll' | 'auto' | 'clip'
+ overflowY?: 'visible' | 'hidden' | 'scroll' | 'auto' | 'clip'
+ overflowWrap?: 'normal' | 'break-word' | 'anywhere'
+
+ // overscroll / snap / scrolling ergonomics
+ overscrollBehavior?: 'auto' | 'contain' | 'none'
+ overscrollBehaviorX?: 'auto' | 'contain' | 'none'
+ overscrollBehaviorY?: 'auto' | 'contain' | 'none'
+ scrollBehavior?: 'auto' | 'smooth'
+ scrollSnapType?: 'none' | 'x' | 'y' | 'block' | 'inline' | 'both' | string
+ scrollSnapAlign?: 'none' | 'start' | 'end' | 'center'
+ scrollSnapStop?: 'normal' | 'always'
+ scrollMargin?: number | string
+ scrollMarginTop?: number | string
+ scrollMarginRight?: number | string
+ scrollMarginBottom?: number | string
+ scrollMarginLeft?: number | string
+ scrollPadding?: number | string
+ scrollPaddingTop?: number | string
+ scrollPaddingRight?: number | string
+ scrollPaddingBottom?: number | string
+ scrollPaddingLeft?: number | string
+ scrollbarWidth?: 'auto' | 'thin' | 'none'
+ scrollbarColor?: string
+
placeContent?: string
placeItems?: string
placeSelf?: string
+
position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'
- right?: number | string
- rowGap?: number | string
- top?: number | string
+
+ justifyContent?: 'normal' | 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'start' | 'end' | 'left' | 'right' | 'stretch'
+ justifyItems?: 'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' | 'start' | 'end' | 'self-start' | 'self-end' | 'left' | 'right'
+ justifySelf?: 'auto' | 'normal' | 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline' | 'start' | 'end' | 'self-start' | 'self-end' | 'left' | 'right'
+
verticalAlign?: 'baseline' | 'top' | 'middle' | 'bottom' | 'text-top' | 'text-bottom' | 'sub' | 'super'
- width?: number | string
+
zIndex?: number
// visual/theme-related
animation?: string
appearance?: 'none' | 'auto' | 'button' | 'textfield' | 'searchfield' | 'textarea' | 'checkbox' | 'radio'
backdropFilter?: string
+
background?: string
backgroundAttachment?: 'scroll' | 'fixed' | 'local'
backgroundClip?: 'border-box' | 'padding-box' | 'content-box' | 'text'
@@ -77,6 +144,7 @@ export type TagDef = {
backgroundPosition?: string
backgroundRepeat?: 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat' | 'space' | 'round'
backgroundSize?: 'auto' | 'cover' | 'contain'
+
border?: string
borderBottom?: string
borderBottomColor?: string
@@ -102,54 +170,110 @@ export type TagDef = {
borderTopStyle?: 'none' | 'solid' | 'dashed' | 'dotted' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset' | 'hidden'
borderTopWidth?: number | string
borderWidth?: number | string
+
+ // table-ish
+ borderCollapse?: 'collapse' | 'separate'
+ borderSpacing?: number | string
+ captionSide?: 'top' | 'bottom'
+ emptyCells?: 'show' | 'hide'
+ tableLayout?: 'auto' | 'fixed'
+
boxShadow?: string
clipPath?: string
+
color?: string
content?: string
cursor?: 'auto' | 'default' | 'none' | 'context-menu' | 'help' | 'pointer' | 'progress' | 'wait' | 'cell' | 'crosshair' | 'text' | 'vertical-text' | 'alias' | 'copy' | 'move' | 'no-drop' | 'not-allowed' | 'grab' | 'grabbing' | 'e-resize' | 'n-resize' | 'ne-resize' | 'nw-resize' | 's-resize' | 'se-resize' | 'sw-resize' | 'w-resize' | 'ew-resize' | 'ns-resize' | 'nesw-resize' | 'nwse-resize' | 'col-resize' | 'row-resize' | 'all-scroll' | 'zoom-in' | 'zoom-out'
+
filter?: string
+
+ font?: string
fontFamily?: string
fontSize?: number | string
fontStyle?: 'normal' | 'italic' | 'oblique'
fontWeight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 'normal' | 'bold' | 'bolder' | 'lighter' | number
+ fontStretch?: string
+ fontVariant?: string
+ fontKerning?: 'auto' | 'normal' | 'none'
+
isolation?: 'auto' | 'isolate'
letterSpacing?: number | string
lineHeight?: number | string
+
listStyle?: string
listStyleImage?: string
listStylePosition?: 'inside' | 'outside'
listStyleType?: 'none' | 'disc' | 'circle' | 'square' | 'decimal' | 'decimal-leading-zero' | 'lower-roman' | 'upper-roman' | 'lower-alpha' | 'upper-alpha' | 'lower-greek' | 'lower-latin' | 'upper-latin'
+
mixBlendMode?: 'normal' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten' | 'color-dodge' | 'color-burn' | 'hard-light' | 'soft-light' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity'
+
objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
+
opacity?: number
+
outline?: string
outlineColor?: string
outlineOffset?: number | string
outlineStyle?: 'none' | 'solid' | 'dashed' | 'dotted' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset'
outlineWidth?: number | string
+
+ // form / selection / interaction
+ caretColor?: string
+ accentColor?: string
pointerEvents?: 'auto' | 'none' | 'visiblePainted' | 'visibleFill' | 'visibleStroke' | 'visible' | 'painted' | 'fill' | 'stroke' | 'all'
resize?: 'none' | 'both' | 'horizontal' | 'vertical' | 'block' | 'inline'
- scrollBehavior?: 'auto' | 'smooth'
+ touchAction?: 'auto' | 'none' | 'pan-x' | 'pan-y' | 'manipulation' | string
+ userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all'
+
+ // writing / bidi / hyphenation
+ direction?: 'ltr' | 'rtl'
+ writingMode?: 'horizontal-tb' | 'vertical-rl' | 'vertical-lr' | string
+ unicodeBidi?: 'normal' | 'embed' | 'bidi-override' | 'isolate' | 'isolate-override' | 'plaintext'
+ hyphens?: 'none' | 'manual' | 'auto'
+ tabSize?: number | string
+
textAlign?: 'left' | 'right' | 'center' | 'justify' | 'start' | 'end'
textDecoration?: string
textDecorationColor?: string
textDecorationLine?: 'none' | 'underline' | 'overline' | 'line-through' | 'blink'
textDecorationStyle?: 'solid' | 'double' | 'dotted' | 'dashed' | 'wavy'
textDecorationThickness?: number | string
+ textUnderlineOffset?: number | string
textIndent?: number | string
textOverflow?: 'clip' | 'ellipsis' | string
textShadow?: string
textTransform?: 'none' | 'capitalize' | 'uppercase' | 'lowercase' | 'full-width' | 'full-size-kana'
- transform?: string
- transition?: string
- userSelect?: 'auto' | 'none' | 'text' | 'contain' | 'all'
- visibility?: 'visible' | 'hidden' | 'collapse'
whiteSpace?: 'normal' | 'nowrap' | 'pre' | 'pre-wrap' | 'pre-line' | 'break-spaces'
- willChange?: 'auto' | 'scroll-position' | 'contents'
wordBreak?: 'normal' | 'break-all' | 'keep-all' | 'break-word'
wordSpacing?: number | string
wordWrap?: 'normal' | 'break-word' | 'anywhere'
- overflowWrap?: 'normal' | 'break-word' | 'anywhere'
+
+ transform?: string
+ transformOrigin?: string
+ transformStyle?: 'flat' | 'preserve-3d'
+ perspective?: number | string
+ perspectiveOrigin?: string
+ backfaceVisibility?: 'visible' | 'hidden'
+
+ transition?: string
+ visibility?: 'visible' | 'hidden' | 'collapse'
+ willChange?: 'auto' | 'scroll-position' | 'contents'
+
+ // masks (if you want modern visual effects)
+ mask?: string
+ maskImage?: string
+ maskSize?: string
+ maskPosition?: string
+ maskRepeat?: string
+
+ // svg styling (if you want these supported)
+ fill?: string
+ stroke?: string
+ strokeWidth?: number | string
+ strokeLinecap?: 'butt' | 'round' | 'square'
+ strokeLinejoin?: 'miter' | 'round' | 'bevel'
+ strokeDasharray?: number | string
+ strokeDashoffset?: number | string
}
export const NonStyleKeys = new Set([