Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/optional-baseline-css.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@formhaus/core": minor
---

Added optional baseline stylesheet at `@formhaus/core/style.css`. Import it for sensible default styling on the React and Vue native field components: padding, focus state, error colour, button styles, step progress bar.

```ts
import '@formhaus/core/style.css';
```

Theme via CSS custom properties (`--fh-color-primary`, `--fh-radius`, `--fh-gap`, etc.) — no fork required. The stylesheet only targets the default `fh-*` classes; if you swap in your own components via the `components` prop, it doesn't touch them.

The package's `sideEffects` field is now `["**/*.css"]` (was `false`) so bundlers preserve the import.
10 changes: 10 additions & 0 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ npm install @formhaus/vue # Vue

Or use `@formhaus/core` directly with any framework. See the [Svelte example in the playground](/playground#svelte).

### Optional baseline styles

The default React and Vue components render unstyled HTML. For a starting look (padding, focus, error colour) import the shared stylesheet:

```ts
import '@formhaus/core/style.css';
```

Theme via CSS custom properties (`--fh-color-primary`, `--fh-radius`, `--fh-gap`, etc.). When you bring your own components via the `components` prop, the stylesheet doesn't apply to them.

## Quick start

```ts
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"test": "pnpm -r run test",
"clean": "pnpm -r run clean",
"lint:packages": "pnpm --filter @formhaus/core --filter @formhaus/react --filter @formhaus/vue exec publint",
"check:types": "pnpm --filter @formhaus/core --filter @formhaus/react --filter @formhaus/vue exec attw --pack --profile esm-only",
"check:types": "pnpm --filter @formhaus/core --filter @formhaus/react --filter @formhaus/vue exec attw --pack --profile esm-only --exclude-entrypoints ./style.css",
"changeset": "changeset",
"version": "changeset version",
"release": "pnpm build && changeset publish"
Expand Down
10 changes: 10 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ engine.getSubmitValues();
// { name: 'Jane', email: 'jane@example.com' }
```

## Optional baseline styles

The default React and Vue field components render unstyled HTML with `fh-*` class names. For a sensible starting look (padding, focus states, error colour, button styles), import the optional stylesheet:

```ts
import '@formhaus/core/style.css';
```

Theme via CSS custom properties (`--fh-color-primary`, `--fh-radius`, `--fh-gap`, etc.) — no need to fork the file. If you're using a UI kit through the `components` prop, the stylesheet doesn't affect those.

## What it covers

- Form state with reactive `subscribe()` / `getSnapshot()` for adapters
Expand Down
10 changes: 7 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
"version": "0.3.1",
"description": "Framework-agnostic form engine with validation, conditional visibility, and multi-step support. Define forms as plain JSON, render anywhere.",
"type": "module",
"sideEffects": false,
"sideEffects": [
"**/*.css"
],
"engines": {
"node": ">=18"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"./style.css": "./style.css"
},
"files": [
"dist"
"dist",
"style.css"
],
"scripts": {
"build": "tsup",
Expand Down
217 changes: 217 additions & 0 deletions packages/core/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* @formhaus/core baseline styles.
* Optional. Import from React or Vue apps:
*
* import '@formhaus/core/style.css';
*
* Override any custom property below to retheme without forking the CSS.
*/

:where(.fh-form) {
--fh-color-text: #111827;
--fh-color-text-muted: #6b7280;
--fh-color-border: #d1d5db;
--fh-color-border-focus: #2563eb;
--fh-color-error: #dc2626;
--fh-color-bg: #ffffff;
--fh-color-bg-disabled: #f3f4f6;
--fh-color-primary: #2563eb;
--fh-color-primary-hover: #1d4ed8;
--fh-color-primary-text: #ffffff;
--fh-color-secondary-border: #d1d5db;
--fh-radius: 6px;
--fh-gap: 16px;
--fh-input-padding: 8px 12px;
--fh-font-size: 14px;
--fh-font-size-small: 13px;
}

.fh-form {
display: flex;
flex-direction: column;
gap: var(--fh-gap);
color: var(--fh-color-text);
font-size: var(--fh-font-size);
}

.fh-form__fields {
display: flex;
flex-direction: column;
gap: var(--fh-gap);
}

.fh-field {
display: flex;
flex-direction: column;
gap: 4px;
}

.fh-field__label {
font-weight: 500;
}

.fh-field__required {
color: var(--fh-color-error);
}

.fh-field__input {
padding: var(--fh-input-padding);
border: 1px solid var(--fh-color-border);
border-radius: var(--fh-radius);
background: var(--fh-color-bg);
color: inherit;
font: inherit;
font-size: var(--fh-font-size);
width: 100%;
box-sizing: border-box;
}

.fh-field__input:focus {
outline: none;
border-color: var(--fh-color-border-focus);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}

.fh-field__input:disabled {
background: var(--fh-color-bg-disabled);
cursor: not-allowed;
}

.fh-field__input[aria-invalid="true"] {
border-color: var(--fh-color-error);
}

.fh-field__input--textarea {
resize: vertical;
min-height: 80px;
}

.fh-field__error,
.fh-form__top-error {
color: var(--fh-color-error);
font-size: var(--fh-font-size-small);
margin: 0;
}

.fh-field__helper {
color: var(--fh-color-text-muted);
font-size: var(--fh-font-size-small);
margin: 0;
}

.fh-form__top-errors {
display: flex;
flex-direction: column;
gap: 4px;
}

.fh-field--checkbox .fh-field__checkbox-wrapper,
.fh-field--switch .fh-field__switch-wrapper {
display: flex;
align-items: center;
gap: 8px;
}

.fh-field__radio-group,
.fh-field__multiselect-group {
display: flex;
flex-direction: column;
gap: 6px;
}

.fh-field__radio-option,
.fh-field__multiselect-option {
display: flex;
align-items: center;
gap: 8px;
}

.fh-form-actions {
display: flex;
gap: 8px;
align-items: center;
}

.fh-form-actions__secondary {
display: flex;
gap: 8px;
margin-left: auto;
}

.fh-form-actions__button {
padding: 8px 16px;
border-radius: var(--fh-radius);
border: 1px solid transparent;
background: transparent;
color: inherit;
font: inherit;
font-size: var(--fh-font-size);
cursor: pointer;
}

.fh-form-actions__button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.fh-form-actions__button--primary {
background: var(--fh-color-primary);
color: var(--fh-color-primary-text);
}

.fh-form-actions__button--primary:hover:not(:disabled) {
background: var(--fh-color-primary-hover);
}

.fh-form-actions__button--secondary {
border-color: var(--fh-color-secondary-border);
}

.fh-form-actions__button--secondary:hover:not(:disabled) {
background: var(--fh-color-bg-disabled);
}

.fh-form-actions__button--text {
text-decoration: underline;
padding-left: 8px;
padding-right: 8px;
}

.fh-step-progress {
display: flex;
flex-direction: column;
gap: 8px;
}

.fh-step-progress__info {
display: flex;
flex-direction: column;
gap: 2px;
}

.fh-step-progress__counter {
color: var(--fh-color-text-muted);
font-size: var(--fh-font-size-small);
}

.fh-step-progress__title {
font-weight: 600;
}

.fh-step-progress__description {
color: var(--fh-color-text-muted);
font-size: var(--fh-font-size-small);
}

.fh-step-progress__bar {
height: 4px;
background: var(--fh-color-bg-disabled);
border-radius: 999px;
overflow: hidden;
}

.fh-step-progress__fill {
height: 100%;
background: var(--fh-color-primary);
transition: width 200ms ease-out;
}
10 changes: 10 additions & 0 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ Unmapped field types fall back to native HTML.

`FormRenderer` is SSR-safe. It works with Next.js static and dynamic prerender, plus React's `renderToString`, with no `next/dynamic` wrapper required.

## Optional baseline styles

By default the React adapter renders unstyled HTML. For a sensible starting look (padding, focus states, error colour, button styles) import the shared stylesheet:

```ts
import '@formhaus/core/style.css';
```

Theme via CSS custom properties (`--fh-color-primary`, `--fh-radius`, `--fh-gap`, etc.). If you bring your own components via the `components` prop (MUI, Tailwind, shadcn), the stylesheet doesn't apply to those.

## Docs

- Full guide and API reference: https://formhaus.dev
Expand Down
11 changes: 11 additions & 0 deletions packages/vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ defineEmits<{ (e: 'update:value', value: unknown): void }>();

Unmapped field types fall back to native HTML.

## Optional baseline styles

By default the Vue adapter renders unstyled HTML. For a sensible starting look (padding, focus states, error colour, button styles) import the shared stylesheet from `@formhaus/core`:

```ts
// main.ts
import '@formhaus/core/style.css';
```

Theme via CSS custom properties (`--fh-color-primary`, `--fh-radius`, `--fh-gap`, etc.). If you bring your own components via the `components` prop (Vuetify, Element Plus, Naive UI), the stylesheet doesn't apply to those.

## Docs

- Full guide and API reference: https://formhaus.dev
Expand Down
Loading