Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@lit-labs/forms package #8

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

@lit-labs/forms package #8

wants to merge 6 commits into from

Conversation

Christian24
Copy link

Hello everyone,

this is a follow up from this discussion in the main repo. I know this is extremely vague and probably very far away from being implemented (if this ever gains traction), but it is the biggest pain point I have using web components at work and since @UserGalileo and myself have experimented with different solutions quite a bit (we actually use a similar in house solution in production), I thought I would compile it into the first RFC.

If someone has a completely different idea on how to handle this, please reach out, my goal is to improve the developer experience using forms, not promote this exact solution. :)

Cheers,
Christian

Copy link
Contributor

@justinfagnani justinfagnani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the RFC!

Here's some initial feedback about making the motivation and some of the API more clear to those not familiar with this area.

rfcs/0000-rfc-forms.md Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
rfcs/0000-rfc-forms.md Outdated Show resolved Hide resolved
@UserGalileo
Copy link

Thank you Christian! I added some info here and there in response to Justin's questions :)

@Christian24
Copy link
Author

Thanks @UserGalileo for the answers. Thanks for the feedback @justinfagnani.

@43081j
Copy link

43081j commented Feb 1, 2023

no clue if this is still actively being pushed or anything but i almost started building a similar thing recently, so just a couple of thoughts...

you have a builder doing stuff like this:

this.fb.group({
    user: this.fb.group({
        name: this.fb.control(''),
        surname: this.fb.control(''),
    }),
    consent: this.fb.control(false)
})

which you actually seem to use as an object schema, not anything to do with a form.

i.e. you're defining the schema of the complex object your form will be updating. those group objects are not groups but are nested object schemas, and those control objects are primitive schemas.

if you were building up a "form", i'd expect those objects lead to you literally having some form groups and inputs automatically produced (into the DOM) but that doesn't seem to be what this is.

which then leads me to wonder, aren't you verging on having just made your own JSON schema basically?

lets say i had an object schema:

{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "surname": {"type": "string"}
      }
    },
    "consent": {"type": "boolean"}
  }
}

some function (the directive in this case) could quite easily traverse the object schema to figure out what setting user.name involves (and if its even possible at all).

totally unrelated to lit or DOM at this point, you'd have basically built a way to automatically bind event listeners and update a complex object correctly based on a schema.

i guess my point is to be careful about inventing your own meta-schema, and to be clear that the form builder isn't building a form, its building some object mutation thing.

also prior art to this - loads of json schema form generators out there in the wild. some defined their own meta-schema (itself a json schema) that can hold UI config (default values, validation, etc). some went with having two schemas (a JSON schema for the data, and a separate form schema for ui control stuff).

@justinfagnani
Copy link
Contributor

Coming back to this after a while (sorry!) I'm trying to think of how we can frame the discussion and design in a way that's understandable for the team members who aren't familiar with such form libraries - so not assuming that the existing library shapes make sense to us :)

I'd love to see this broken down into problems and goals before getting terribly deep into the details of a solution.

For instance, I think that my understanding of these are:

Problems:

  • Conceptually, complex forms are editing a complex object, and we would like an ergonomic way of declaring that object (the "model")
  • Validation at the UI component level isn't optimal, we'd like to validate in the model, especially for cross-field validation.
  • Binding a complex model into UI components is cumbersome. Without two-way binding in Lit, we at least have to bind both the value into the component, and add an event listener to update the model.
    • Validation makes this harder, since you also want to bind the validity state and error messages.
    • Conditional enabled / disabled, and conditional data (ex. drop-down options dependent on another field) are additional binding complications
  • Form UIs need to update when the model data changes (we need to explain why the default top-down dataflow isn't sufficient)

Goals:

  • A declarative API for defining a form model, separate from the form UI
  • Easy way to bind multiple pieces of stand and event listeners into a form control
  • Validation declaration that can work with single or multi-field validators
  • Works with arbitrary UI controls (though should it be easier if the control conforms to a protocol?)

Broken down this way, this sounds a lot to me like a custom state management solution - akin to Redux, MobX, or maybe signals, but specifically for forms.

This makes me wonder if the way forward forks into three possible paths:

Possible solutions:

  • Build a custom state management system for forms
  • Reuse an existing generic state management system
  • Build a forms-specific layer on top of an existing system

Signals

I would specifically like to call out and talk about the possibly of signals playing a role here. Signals are interesting because they are more atomic containers of observable state, and they may enable two-way binding without string-based paths as currently in the RFC.

The example in the RFC:

render() {
  const { bind } = this.form;
  
  return html`
    <input type="text" ${bind('user.name')}>
    <input type="text" ${bind('user.surname')}>
    <input type="text" ${bind('consent')}>
  `;
}

Could possibly be re-written as:

render() {
  return html`
    <input type="text" ${bind(this.form.user.name)}>
    <input type="text" ${bind(this.form.user.surname)}>
    <input type="text" ${bind(this.form.consent)}>
  `;
}

Where each leaf in the model is a signal with set and get APIs (Preact signals uses a .value accessor for instance). bind() would bind .value on the input and listen for the change event by default. The signal's value could be the entire form field state: value, isValid, validity message, etc. Custom validators and dynamic validity messages could be computed signals nested in the field's value. Conditional data could be represented as a computed signal too.

Bindings

It seems like most solutions here would propose a higher-level binding mechanism. I think this part of the RFC is a good start on identifying what it needs to do:

  • change event
  • how to trigger an element's builtin validation (if exists)
  • how to get and set the element's current value
  • how to set an error message on the element (if available)
  • how to change the element's state

but I think we need to flesh this out and include some examples (like how would one write a bind directive for MWC or Shoelace's input?). We might want to see if libraries would ship their own directives, since if users have to set this up its not much different from writing all the low-level bindings - it's only a win if they bind to the same type of component many times.

@UserGalileo
Copy link

UserGalileo commented May 24, 2023

Yeah basically what this would involve is 2 basic things:

  • A state management mechanism specifically designed for forms (in my examples, the controllers) which stores as a single source of truth not only values but also states (disabled, readonly... touched, blurred, dirty... validity states...)
  • A way to 2-way bind this state to the UI (the bind directive)

Again, this would be a model-driven approach, with no progressive enhancement in sight imho. It's to decide if Progressive Enhancement would be worth it here, because to me (personal opinion) it's not, especially because with the new Form Associated Elements API it'll be the same story (if I'm not mistaken there won't be a declarative approach so no progressive enhancement a-la remix, too).

So before even talking about this it's imho necessary to evaluate whether to go with a model-driven approach or a template-driven approach more towards standards [with its merits (eg. less code if you don't want to type the form, progressive enchancement) and downsides (eg. no nesting for progressive enhancement)]. For a template-driven approach, I think something like React Hook Form could be very cool (I don't really like the specific API, especially for complex forms and form arrays, but it's a detail).


I'd personally be surprised to see Signals in user land here. It's another concept to add, which seems unnecessary for the developer to know if all it does is to avoid the string paths (which would be inferred correctly by TS anyway, so I see no practical difference).

As for 3rd party components, I wouldn't expect library authors to ship anything: all the developer has to do to wire it is tell the directive:

  • The events of the component
  • The names of the properties for values, disabled, etc.

In my POC it looked something like this, suppose we want to wire a my-counter element:

const counterBindConfig = {
  changeEvent: 'counterChange',
  focusEvent: 'counterFocus',
  blurEvent: 'counterBlur',
  accessor: (el: Counter) => ({
    getValue: () => el.value,
    setValue: x => el.value = x,
    setDisabled: is => el.disabled = is
  })
}
<my-counter ${bind('counter', counterBindConfig)}></my-counter>

This is similar to what we do in Angular with custom form controls, too.

@justinfagnani
Copy link
Contributor

@UserGalileo

I'd personally be surprised to see Signals in user land here. It's another concept to add, which seems unnecessary for the developer to know if all it does is to avoid the string paths (which would be inferred correctly by TS anyway, so I see no practical difference).

I bring up signals because of the overlap in some of the problem/solution space. I really don't like the string-based API of bind('form.user.name'), etc. Signals have get and set, so if you can get to them by reference you can two-way bind to them. References are better for intellisense and easier to type check (even if TypeScript does support parsing string literals with enough type magic).

Signals also apply to domains other than forms, so it might be fewer concepts to learn if someone's using signals elsewhere.

The observable forms model looks so much like a state management system that I'm not sure it shouldn't just be based on an existing system rather than building a new one. Signals just happen to usually be one of the simpler and lightest weight options.

@daKmoR
Copy link

daKmoR commented May 25, 2023

Not sure if that is the right spot but there seems to be two kind of different needs?

  1. have a model (in json) and map it to a lit-html template
  2. have a model + declaration of layout (both in json) and create the html from it (as string, lit-template, ...)

I guess in many cases if there is a need for a "model" to manage forms then usually it also means you probably somehow what to display that form in the "models format as json"... e.g. for "smaller stuff" you can handle it "manually" as soon as you need a "complex" model you probably also want a "layout system"

funny enough I am exactly in need of something like that so I have done some exploring but nothing really came up 😅

potentially something like
https://github.com/jsonform/jsonform but "modern" e.g. not a jquery plugin and it can run in node (and the browser) and the layout/templates/conditions could be lit-templates

My Use case is a list of documents and each document needs different data to be filled in.
now I want to define all the possible "questions" somewhere and then only say.
document A needs questions 1, 5 and 12.
document B needs questions 1, 50 and 51

Would need to support

  • question dependencies
  • a way to order/arrange/layout questions
  • validation
  • ... and probably lots more I can't think of right now 🤣

PS: throwing this in here - sorry if it doesn't fit correctly... it just came up at the same time 🤗

@UserGalileo
Copy link

@justinfagnani

Disclaimer: I like signals! And I wouldn't be against them. My doubt comes from the fact that there isn't a single signal implementation that fits all (it's not a standard, or a community protocol or whatever iirc, for instance Observables seem more like a potential standard to me). Each system has its own caveats so there'll always be small (but noticeable) differences between one approach and the other. I like signals in frameworks (Angular, Vue, solid, preact) because they solve a major problem they all have: change detection (granular, in that case). Signals are the core for reactive templates. With Template Literals I'm not sure this is a big problem to solve? If Lit proposes something based on signals, one could ask then why not using them also for properties and states? Would signals play a role in other scenarios in Lit or would they be limited to forms? Just brainstorming!

(I still fail to see how strings would be worse than references but that's secondary and I may be missing something :P)

Anyway, I'm curious: how do you see the Progressive Enhancement matter? (Specifically, submitting the form without JS, taking into account the Form Association API) I think that's a very important discriminant.


@daKmoR

It seems you're talking about something like Formly for Angular? That's a different beast :P I'd love to have something like it too, but that'd be a second step!

@danduh
Copy link

danduh commented May 30, 2023

Hi I'll try to jump into conversation...

IMHO, we are trying to solve as many issues as possible and cover the maximum possible use cases.
I think it comes from the fact that we know how Angular forms work, and we probably want similar possibilities in lit-form out of the box from version 1.0.0

What if we can start with something smaller?

Let's solve one simple use-case for the registration form:
Simple form, with no nested forms.

username - string, required
firstname - string, optional
lastname - string, optional
email - string, required, email validation
password - string, required, regex validation

WTD?

@43081j
Copy link

43081j commented May 31, 2023

i've written several implementations of things like this over the years.

the latest one does what justin mentioned via strings:

html`
  <form ${ctrl.attach()}>
    <input type="text" ${ctrl.bind('email')}>
    <input type="text" ${ctrl.bind('name.first')}>
    <input type="text" ${ctrl.bind('name.last')}>
  </form>
`;

// elsewhere...

ctrl.addValidator((model) => {
  // do some validation
});

which results in {email, name: {first, last}}.

this is a fairly basic approach and, i agree, would be made nicer if the strings were actual references.

other approaches i've tried in the past:

  • JSON schema - pass a schema in, the entire form is generated, the object is built up like above
    • This didn't go down well since you lose control of the UX without great effort into making it customisable
  • Custom form schema - pass a purpose-built form schema in
    • Entire form is generated like with JSON schema
    • Since it is a custom schema, each field can be given "presentational" properties to customise UX
    • Still didn't go down well with the team since you still end up with generic/difficult-to-customise UX
  • Auto detect fields of an attached form and build up output object
    • In our case, our engineers didn't like this much as it involved too much "magic" (i.e. they couldn't see what was bound to what)

i think @danduh is right too in that this is potentially a very large RFC. if we try solve both sides of it (form generation & form binding) in one, its a very deep rabbit hole and we might get stuck bikeshedding.

i would say the basic form support would involve:

  • ability to easily two-way bind a form field to something on the model
  • ability to build up said model via those bindings
  • ability to add validators to prevent invalid model updates and give ui feedback

i'd expect to use the native submit event to know when i need to take that model and send it off wherever (no custom event needed ideally).

separately, form generation could be tackled. i.e. something which generates the form DOM for you.

@jpzwarte
Copy link

jpzwarte commented Jul 12, 2023

As a design system author I am personally interested in remaining as close as possible to the native API. That means:

  • Form associated custom elements by using ElementInternals
  • checkValidity, reportValidity etc etc.
  • :user-invalid, :user-valid etc.
  • const formData = new FormData(form)
  • <x-form-control name="foo" required>

What I am missing from those APIs is:

  • custom validators are left up to the developer: we would all benefit if Lit provided an API for this; one that eventually called setCustomValidity on the host element; <x-form-control name="foo" required .validators=${[MyCustomAsyncValidator]}>?
  • no easy way to set data for an entire form: you would have to iterate over form.elements and set the value for each

Not sure if I need anything here:

  • you have to manually listen for a "change/input" event and assign the value to a local property: This seems fine; not sure if I need a Lit directive to make this easier. What are the scenarios where you would need this?

Ideas that Lit could do:

  1. Add lit directive for custom validators on built-in-elements: <input name="foo" ${customValidators(...)}>?
  2. Add lit validator support for form associated custom elements. Controller, mixin?
  3. Add lit directive for form data: <form ${formData({ foo: 'bar' })>; perhaps this could be just the JSON you get back from a server; or fully Angular ReactiveFormsModule?
  4. Make it easier to implement built-in validation like the required attribute in <x-input name="foo" required>

@jpzwarte
Copy link

Kind of a core question to this entire RFC: should lit provide an opinionated (non-declarative?) API for form integration (like Angular), or should lit provide low-level support and leave the rest up to users? Or both? Or start with low-level and see what happens?

@rictic
Copy link

rictic commented Jul 14, 2023

Discussed in our RFC review meeting.

Implementing and maintaining our own forms model involves making many implementation choices, and will be an ongoing source of maintenance work. A small wrapper around an existing library will likely be an order of magnitude less code for us to maintain. One option for a library to wrap is final-form, which seems reasonably small and is published as modules; see this proof of concept by Augustine: lit/lit#2489 (comment). It does seem like there's been fairly light traffic on the final-form github the past few months though, maybe there's a better option?

Would final-form match your use cases @Christian24? If so, we'd like to see this RFC updated to build on top of it, or another similar library.

@usergenic
Copy link

Kind of a core question to this entire RFC: should lit provide an opinionated (non-declarative?) API for form integration (like Angular), or should lit provide low-level support and leave the rest up to users? Or both? Or start with low-level and see what happens?

In terms of low-level support, there are at least a handful of separately usefully concerns:

  • "Dirty" status (i.e. whether anything has changed in a field/form from initial state)
  • Form/field validation
  • Form data serialization/deserialization
  • Multi-step form flows i.e. "wizards".

I'm sure there are others, including support methods and examples for aria/accessibility concerns as well. If we kept them usable as separate modules and made it easy to use/mixin each in as needed/desired, that might be closer to our overall design philosophy for other packages and avoids creating a forms package that is overly "frameworky".

@marcomuser
Copy link

Discussed in our RFC review meeting.

Implementing and maintaining our own forms model involves making many implementation choices, and will be an ongoing source of maintenance work. A small wrapper around an existing library will likely be an order of magnitude less code for us to maintain. One option for a library to wrap is final-form, which seems reasonably small and is published as modules; see this proof of concept by Augustine: lit/lit#2489 (comment). It does seem like there's been fairly light traffic on the final-form github the past few months though, maybe there's a better option?

Would final-form match your use cases @Christian24? If so, we'd like to see this RFC updated to build on top of it, or another similar library.

Fyi, there is renewed development effort over in the tanstack family of libraries to get a framework agnostic form state management library off the ground. It's early days but considering the success around the other tanstack libraries, first and foremost tanstack query and table, I think it could be a reasonable alternative to final-form. They share a very similar api either way. Right now there is already a core package (final-form equivalent) and a react wrapper (react-final-form equivalent). See: https://github.com/TanStack/form

@Christian24
Copy link
Author

First of all sorry everyone for being silent on the matter. I have changed projects and I now convert Cobol applications to Java...

Therefore I lost track of what happened here. I thought the idea in general hadn't gained enough traction.

Discussed in our RFC review meeting.

So, sorry, is there a regular schedule? Then I will try to make the next meeting.

Implementing and maintaining our own forms model involves making many implementation choices, and will be an ongoing source of maintenance work. A small wrapper around an existing library will likely be an order of magnitude less code for us to maintain. One option for a library to wrap is final-form, which seems reasonably small and is published as modules; see this proof of concept by Augustine: lit/lit#2489 (comment). It does seem like there's been fairly light traffic on the final-form github the past few months though, maybe there's a better option?
Would final-form match your use cases @Christian24? If so, we'd like to see this RFC updated to build on top of it, or another similar library.

From a brief look at it, it would cover my use case. What would be interesting is to make the wrapper work with the existing ecosystem. From what I understand form data is by default not typed, so this companion library would also need to be used.

Fyi, there is renewed development effort over in the tanstack family of libraries to get a framework agnostic form state management library off the ground. It's early days but considering the success around the other tanstack libraries, first and foremost tanstack query and table, I think it could be a reasonable alternative to final-form. They share a very similar api either way. Right now there is already a core package (final-form equivalent) and a react wrapper (react-final-form equivalent). See: https://github.com/TanStack/form

I have not used Tanstack before, so I cannot really comment on it.

I agree using an existing library is probably the way to go. To me one of the central questions is what would the Lit-API for custom controls look like? We would need some kind of wrapper to to allow usage of existing controls like Material Web Components for example. React Final Form solves it like this, not sure what our API would look like.

@marcomuser
Copy link

From a brief look at it, it would cover my use case. What would be interesting is to make the wrapper work with the existing ecosystem. From what I understand form data is by default not typed, so this companion library would also need to be used.

Not the main point of your post but I thought I'll quickly leave this info here anyway as a side note. Final-form 4.14 introduced typed form values. This was exposed in react-final-form 6.1 via a withTypes helper fn. It's sadly not documented anywhere besides the release notes. But I remember using this in the past on a previous project.

@augustjk
Copy link

augustjk commented Sep 1, 2023

My POC did take advantage of typed form values for final-form. Here it is copied over to stackblitz instead of the lit.dev playground as it shows type info on hover (though unfortunately lit templates don't get syntax highlighting): https://stackblitz.com/edit/vitejs-vite-vci6ei?file=src%2Findex.ts

The control is mainly done by using a directive to add event listeners for focus, blur, and input events on the element, and updating value/checked properties on the element. Any custom form components should work if they follow closely to native elements. Here it is with material web components: https://stackblitz.com/edit/vitejs-vite-nhy8zg?file=src%2Findex.ts
This also takes advantage of the component's built in error display. The only werid thing I had to do was add .type={'checkbox'} for the <md-checkbox> as that's how the directive determined which property to store the value in.

A more robust control might have an option to actually use HTML5 built in validation API like setCustomValidity() and reportValidity() too.

Tanstack form also looks potentially promising. I'll have to check it out and see what an adapter for that might look like.

@Christian24 the RFC meetings have been ad-hoc but we try and announce them ahead of time on our discord server https://lit.dev/discord We'd certainly love to have you for our next one!

@Christian24
Copy link
Author

Would final-form match your use cases @Christian24? If so, we'd like to see this RFC updated to build on top of it, or another similar library.

From a brief look at it, it would cover my use case. What would be interesting is to make the wrapper work with the existing ecosystem. From what I understand form data is by default not typed, so this companion library would also need to be used.

I had a longer look at it and I see potential problems with using final-form:

  • Grouping form elements together is quite common. This can probably be done by altering the structure of the Final-form config.
  • I have often seen forms that have these groups as arrays. So you have a group person, which has first name, last name and age for example. And then on your form you can add or delete any number of persons you like. You can do this easily in Angular with FormArray. Final-form seems to have something similar, but only for its react counterpart as far as I am aware: https://github.com/final-form/react-final-form-arrays/tree/master

I have seen Tanstack forms exploring FormGroups: TanStack/form#419

Maybe I should open an issue at Tanstack about FormArrays there?

Digging a bit, I also found this: https://github.com/final-form/final-form-calculate Which I found to be quite an interesting idea. Given this is quite a common use case. You change something in a form and other elements get cleared or calculated.

Sadly, it doesn't look maintained.

The control is mainly done by using a directive to add event listeners for focus, blur, and input events on the element, and updating value/checked properties on the element. Any custom form components should work if they follow closely to native elements. Here it is with material web components: https://stackblitz.com/edit/vitejs-vite-nhy8zg?file=src%2Findex.ts This also takes advantage of the component's built in error display. The only werid thing I had to do was add .type={'checkbox'} for the <md-checkbox> as that's how the directive determined which property to store the value in.

A more robust control might have an option to actually use HTML5 built in validation API like setCustomValidity() and reportValidity() too.

We should allow customizing of all this behavior. I think people might have an existing control library, they don't want to change.

Tanstack form also looks potentially promising. I'll have to check it out and see what an adapter for that might look like.

I agree. From what I see they wrap elements in their own controls. https://github.com/TanStack/form/blob/c6b246de9e47546716c41790330e7ee2d9d380ff/examples/react/simple/src/index.tsx#L41 It is a very different than just having a model or directive. Just feels different. I also need some time to play with it some more.

@Christian24 the RFC meetings have been ad-hoc but we try and announce them ahead of time on our discord server https://lit.dev/discord We'd certainly love to have you for our next one!

I'd love to come. To be honest the community lost me a bit due to work changes (I am no longer working with Lit) and I am not on Discord. Maybe it is time to change that.

I have used neither library. So, maybe others can chime in about their experiences. Both seem not to be well documented. My feeling is at the moment both are lacking features. Maybe we need to let them develop a bit first?

@Christian24
Copy link
Author

After some more research my view is that @tanstack/forms is not far enough along yet.

I had said that final-form only handles arrays for react, but it seems the underlying package is cross library: https://final-form.org/docs/final-form-arrays/getting-started

Maybe it makes sense to implement a reference form in Angular and then see how this could be adapted to lit with your prototype @augustjk?

@augustjk
Copy link

Maybe it makes sense to implement a reference form in Angular and then see how this could be adapted to lit with your prototype @augustjk?

Some real-world or real-world-like reference would be nice to see as a goal of what kind of features we'd like to see though I don't know if it would really change the final form adapter much. It should be easy enough to add final-form-arrays or any "mutators" to the adapter as it just takes the whole form creation config, and directly exposes the created form instance.

@Christian24
Copy link
Author

Hello @augustjk,

I built a little reference form with arrays and groups: https://github.com/Christian24/forms-example

The Stackblitz apparently has trouble with Angular Material. I will try to set that up tomorrow. There are also no validations etc. Just a general structure of a more complex form.

@Christian24
Copy link
Author

Are the RFC meetings the Open Eng Meetings? I only ever see those when those have been going on for some time...

@augustjk
Copy link

Are the RFC meetings the Open Eng Meetings? I only ever see those when those have been going on for some time...

Open Eng Meetings are weekly meetings where we discuss any engineering topics. Sometimes we get some discussion around an RFC but not always. We do a call for topics and we could totally fit form discussions there too. Otherwise the general RFC meetings have been ad-hoc. We've been focusing on 3.0 related work recently but after that we should look at scheduling another one of those.

@Christian24
Copy link
Author

Christian24 commented Oct 3, 2023

Hey @augustjk,

I spent some time figuring out how to use arrays with final-form: https://stackblitz.com/edit/vitejs-vite-6o5g2z?file=src%2Findex.ts

It actually works out of the box and the mutators are not needed.

EDIT: I did update it now, so both reset and validations work again.

One of the things I noticed is there seems to be no explicit grouping of fields. So you cannot easily do something like "give me an error-object just for this single item of this form array". You always have to query every field individually:

getFieldState("employees[0].firstName")
getFieldState("employees[0].lastName")
getFieldState("employees[0].color")

getFieldState("employees[0]") // returns undefined

If we really wanted something like this, we could probably query getRegisteredFields and build return our own group structure just by determining what is a group to us and querying each value of a group individually and then give back a different structure to the user. My idea here follows the Angular FormGroup.

To make the ugly field paths a bit easier to work with, I just wrap register and getFieldState in array, so users do not have the specify the entire path to the field. Something similar maybe is useful for a group of fields as well.

Other finding: We should not give back FieldState to user land. .error is typed as any, which makes the validations harder than they need to be.

Other observations: The TypeScript types that ship with final-form use keyof. However, according to their documentation other field names are possible (including nesting). I opened an issue. The question is if we would want to support this, because it would make TypeScript types really difficult.

I do feel we should allow some easy way of form nesting too. Maybe there could be a second ReactiveController that dispatches events whenever the form changes? We could then use the register directive in the parent form to make that child element (and form) easily integrate into the parent form.

@crutchcorn
Copy link

Hi all! Sorry I missed this previously. I'm the maintainer of TanStack Form - wanted to drop in with thoughts and an offer to help.

After some more research my view is that @tanstack/forms is not far enough along yet.

Unfortunately, this is true at the moment. Notably, we have three major things we need to resolve first that y'all will likely run into:

However, a few thoughts:

  • We're moving really fast towards the goalpost of finishing these 3 items. We have a good number of contributors eager and actively working on things
  • TanStack Form has a fully framework and runtime (no document usage) agnostic core (we support React + Vue + a PR for Solid I'm merging this week/month) and I'm happy to work with the Lit team to adopt TanStack usage to make integration a reality.

Outside of stability (which we're moving quickly on) and form arrays (which we support but are not well documented) - what else would y'all be missing? How could we help?

@Christian24
Copy link
Author

Hello @crutchcorn,

Thanks for reaching out. I'd be very open to using TanStack Form. I will try to look at the work that has been done on your end tomorrow.

@crutchcorn
Copy link

Sounds good @Christian24 - I'm also in the Lit Discord, so please feel free to tag me in the server or reach out via DMs. I'd also be open to doing a voice call if you'd be interested at all, even if it's helping understand the problem-space or how to best utilize a non-TanStack library (IE: Final Form)

@justinfagnani
Copy link
Contributor

@crutchcorn I'm glad you're in the community! I'd be interesting in joining a call too.

@43081j
Copy link

43081j commented Oct 27, 2023

If such a call happens, me too please! 🙏

It would be good to get this RFC moving more. I guess we could treat it similar to the wrapper work? i.e publish an integration or two with popular form libraries and leave the rest up to the consumer

@augustjk
Copy link

Also interested in call. 🙋 Agree with @43081j we should be open to having multiple integrations with forms libraries, whether they live in @lit-labs or within the form package's ecosystem in the case of tanstack forms.

@Christian24
Copy link
Author

I'd also be interested to do a call. I am not active on Discord much (mostly browsing on my work laptop).

Maybe we can find some time next week?

@crutchcorn I took some time today and adapted @augustjk's prototype to use Tanstack Form: https://stackblitz.com/github/christian24/tanstack-form-lit-prototype?file=src%2Findex.ts

There's currently no array support, but I am quite hopeful I can get that in soon.

A couple of random feedback since I tinkered with the code today:

  • I like Tanstack Form a lot. Getting the user facing API to work was pretty seamless, given all the types you provide like DeepKeys and DeepValueetc.
  • This is the first Tanstack Form wrapper that is somewhat different as it uses directives and not controls. Controls are somewhat difficult given how that would create another shadow dom and that is probably not desirable. Do you have any thoughts on this?
  • I would have loved to have some documentation in the code as to what all the generic parameters do or are supposed to be. Sometimes I just put any, because I couldn't figure it out.
  • I don't know if it is my implementation or default handling in general: If one does not give default values, they are just undefined. However, some of the controls used cannot deal with a value of undefined. The text field for example will initially have a value of '' and then when I reset() the form, it gets turned into undefined, since that is the default value and the control does not know how to deal with that. See https://github.com/Christian24/tanstack-form-lit-prototype/blob/ae2ecee0e11c883877fb32f12d44c6361d55f7dd/src/tanstack-form-controller.ts#L96 for how I set the the default value in the directive and use that if the value coming from the FieldApi is undefined. Is the problem my prototype implementation or should the default value handling be changed in the library itself?
  • Simple HTML based tests would have helped me understand how to setup the library more easily.

Tanstack Form seems like a great library and I am pretty certain a wrapper can be created in no time.

@crutchcorn
Copy link

crutchcorn commented Oct 28, 2023

@Christian24 , thank you immensely for the feedback. There are some known issues with docs and bugs as you mentioned - we're aiming to iron them out here soon.

As for your implementation - while what you've outlined is a simplified, it's not quite aligned with the existing APIs of TanStack Form.

Instead, I was prototyping in the Discord last night and came up with something akin to this - I'd love to hear your thoughts;

Usage

import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
import {FormController} from './form-controller.js';

@customElement('my-form')
class MyForm extends LitElement {
  private form = new FormController(this, {name: "Test"});

  render() {
    const form = this.form;
    return html`
        <form>
          ${form.Field(props => html`
            <label>
              <div>${props.name}</div>
              <input/>
            </label>
            `)}
        </form>
    `;
  }
}

POC Implementation

import {ReactiveController, ReactiveControllerHost, LitElement, html, noChange} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {directive} from 'lit/directive.js';
import { AsyncDirective } from 'lit/async-directive.js';

function getField<T>(opts: T) {
  class Field extends AsyncDirective {
    templFn!: (props: T) => ReturnType<typeof html>
  
    render(templFn: (props: T) => ReturnType<typeof html>) {
      if (this.templFn !== templFn) {
         this.templFn = templFn;
         return this.templFn(opts)
      }
      return noChange;
    }
    // Used in the future to reproduce a component's lifecycle method
    // Required by `@tanstack/form`
    disconnected() {
      console.log("DISCONNECTED");
    }
  }
  return Field;
}


export class FormController<T> implements ReactiveController {
  host: ReactiveControllerHost;

  Field!: ReturnType<typeof directive<ReturnType<typeof getField<T>>>>
  
  constructor(host: ReactiveControllerHost, opts: T) {
    (this.host = host).addController(this);
    this.Field = directive(getField<T>(opts));
  }
  // Used in the future by `@tanstack/form`
  hostConnected() {
  }
  hostDisconnected() {
  }
}

@crutchcorn
Copy link

Ignoring implementation for a moment, this is what the ideal API looks like IMO:

/**
 * This doesn't work today, just a demo
 */
import {LitElement, html, nothing} from 'lit';
import {customElement} from 'lit/decorators.js';
// This could also be `@lit-labs/forms` instead
import {FormController} from '@tanstack/lit-form';
import { zodValidator } from "@tanstack/zod-form-adapter";
import { z } from "zod";

@customElement('my-form')
class MyForm extends LitElement {
  private form = new FormController(this, {
      // Not only does this bind the default values, but it also handles the TS typings,
      // `Field.name` will be forced to be a key of this object, and `onSubmit`'s `values` will be of this object's type
      defaultValues: {
        firstName: '',
        lastName: '',
      },
      onSubmit: async (values) => {
        console.log(values)
      },
      validator: zodValidator,
  });

  render() {
    const form = this.form;
    const onFormSubmit = (e: Event) => {
      e.preventDefault();
      e.stopPropagation();
      void form.handleSubmit();
    };
    return html`
        <form @submit="${onFormSubmit}">
          ${form.Field({            
            name: "firstName",
            onChange: z
              .string()
              .min(3, "First name must be at least 3 characters"),
            onChangeAsyncDebounceMs: 500,
            onChangeAsync: async (value) => {
              await new Promise((resolve) => setTimeout(resolve, 1000))
              return (
                value.includes('error') && 'No "error" allowed in first name'
              )
            },
            render: field => {
              const handleChange = (e: Event) => field.handleChange((e.target as HTMLInputElement).value); 
              return html`
                <label .htmlFor="${field.name}">First Name:</label>
                <input
                  .name="${field.name}"
                  .value="${field.state.value}"
                  @blur="${field.handleBlur}"
                  @input="${handleChange}"
                />
                ${field.errors.length ? html`<ul>
                  ${field.errors.map((err) =>
                    html`<li>${err}</li>`
                  )}
                </ul>` : nothing}
              `
            }
        })}
        </form>
    `;
  }
}

@Christian24
Copy link
Author

Let me get the discord app, it seems like I have missed something ;)

@ITenthusiasm
Copy link

ITenthusiasm commented Dec 3, 2023

@augustjk, I saw you mention an interest in low-cost options in the linked discussion. If the ideal would be to rely on simple, effective solutions that already exist, are there any thoughts on trying out the FormValidityObserver (source code)? It's part of the @form-observer/core NPM package, which provides a stateless, framework-agnostic JavaScript utility that empowers developers to run sophisticated, encapsulated logic easily as users interact with their forms.

Believe it or not, a lot can be accomplished with the native form APIs. You can even keep track of dirty/visited fields (including Web Components) with simple event listeners and data-* attributes. So it would be a shame to have to rebuild all of the browser's form features from scratch using Lit's specific solution for state management -- and it could also be error prone, too. (I've seen React Hook Form accidentally behave in ways that deviate from what's expected of regular web forms in some places... because it's just really hard to re-invent every single form detail in a way that conforms to how browsers behave.)

To me, the ideal is to leverage the browser as much as possible, and I believe that this brings more flexibility than many people realize. This is part of the philosophy of the FormValidityObserver. (More on its philosophy here.)

Right now, I'm on a mission to create integrations for all of the frameworks featured on Vite's Getting Started page. At this time I have 4 out of 7 frameworks down. I'm working on Qwik currently, and was hoping to look at Lit soon. Here's a rough example of an integration in Solid. If you look at the (very tiny) source code, you'll notice that it's pretty easy to spin up a framework integration. (This is the benefit of leaning into the browser and having a framework-agnostic core.) For Lit, the StackBlitz example and the integration source code would look roughly the same as it does for Solid, with some minor changes for Lit's needs.

Benefits of the Solid.js FormValidityObserver (non-exhaustive, similar in the Core and in all JS framework integrations)
  • Builds on top of the Constraint Validation API. (You can leverage min, pattern, etc.)
  • Progressively enhances your forms by falling back to the browser's native validation when JS is disabled.
  • Supports custom validation logic (including async functions).
  • Supports custom error messages and even custom rendering logic for error messages.
  • Works with Web Components.
  • Works with form controls that aren't children of your <form> element.
  • You keep the same API even if you switch frameworks (Svelte, Vue, React, Solid, etc.).
  • Only 3.7kb total when minified + gzipped. (2.9kb Core + 775b Solid Integration ... Smaller than Final Form)

If this seems like something that would be valuable, let me know. I'm actually really excited to create the Lit convenience API because if it works out, it would prove that there are framework agnostic solutions for forms that can integrate fairly seamlessly into any framework -- even if the framework doesn't have props spreading.

CC: @Christian24 and @UserGalileo, as you both seemed quite invested in this discussion.

@Christian24
Copy link
Author

Let me preface this by saying I do not speak for the team and absolutely do not want to discourage you.

@augustjk, I saw you mention an interest in low-cost options in the linked discussion. If the ideal would be to rely on simple, effective solutions that already exist, are there any thoughts on trying out the FormValidityObserver (source code)? It's part of the @form-observer/core NPM package, which provides a stateless, framework-agnostic JavaScript utility that empowers developers to run sophisticated, encapsulated logic easily as users interact with their forms.

The idea of a low-cost option is definitely true. Truth be told nobody really knows how adoption of a Lit form wrapper is going to develop. Could be a hit - could be dead on arrival.

Since @crutchcorn has offered to help (regardless of the library used btw), there were talks about a Tanstack Form Lit adapter. I have pretty much a POC ready and we are trying to finalize the API.

That said nothing is stopping you from creating your own adapter for FormValidityObserver. I welcome people taking part in this in whichever form it may be. I haven't looked at it in detail, but maybe you create the wrapper the community adopts.

However, forms are hard. My two cents are that I would want to use an adapter by a team or library that has seen some adoption and maintenance. For example final-form works, but their TypeScript types and documentation are just incorrect and it looks like not much is happening on that front. Hence why I would vote for Tanstack Form, given @crutchcorn works daily on and with it and the Tanstack team has shown in the past they can and will maintain these packages.

Believe it or not, a lot can be accomplished with the native form APIs. You can even keep track of dirty/visited fields (including Web Components) with simple event listeners and data-* attributes. So it would be a shame to have to rebuild all of the browser's form features from scratch using Lit's specific solution for state management -- and it could also be error prone, too. (I've seen React Hook Form accidentally behave in ways that deviate from what's expected of regular web forms in some places... because it's just really hard to re-invent every single form detail in a way that conforms to how browsers behave.)

To me, the ideal is to leverage the browser as much as possible, and I believe that this brings more flexibility than many people realize. This is part of the philosophy of the FormValidityObserver. (More on its philosophy here.)

While I generally agree with the idea of leveraging the browser as much as possible, I wouldn't agree in this specific case. We tried the browser validation API for our projects a while back and noticed that browser validation for emails was just checking if a string contains @, which wasn't sufficient in our case. There are many of those examples in the browser APIs, which leads me personally to believe that the browser APIs are insufficient.

Right now, I'm on a mission to create integrations for all of the frameworks featured on Vite's Getting Started page. At this time I have 4 out of 7 frameworks down. I'm working on Qwik currently, and was hoping to look at Lit soon. Here's a rough example of an integration in Solid. If you look at the (very tiny) source code, you'll notice that it's pretty easy to spin up a framework integration. (This is the benefit of leaning into the browser and having a framework-agnostic core.) For Lit, the StackBlitz example and the integration source code would look roughly the same as it does for Solid, with some minor changes for Lit's needs.

Benefits of the Solid.js FormValidityObserver (non-exhaustive, similar in the Core and in all JS framework integrations)
If this seems like something that would be valuable, let me know. I'm actually really excited to create the Lit convenience API because if it works out, it would prove that there are framework agnostic solutions for forms that can integrate fairly seamlessly into any framework -- even if the framework doesn't have props spreading.

CC: @Christian24 and @UserGalileo, as you both seemed quite invested in this discussion.

@ITenthusiasm
Copy link

ITenthusiasm commented Dec 4, 2023

I appreciate your honesty and thoughtful response! 😄 Some thoughts

It's helpful to know that some work has already been put in place with the TanStack. I'll happily continue with the POC for the FormValidityObserver to see how it turns out. No hurt feelings if it doesn't get adopted.

However, forms are hard. My two cents are that I would want to use an adapter by a team or library that has seen some adoption and maintenance.

Understandable! As the creator of the library, what's difficult about that is that a library doesn't have significant adoption until it has a significant adoption (even if it's good). But if I was on the consuming side, I would certainly want to know that what I was using was reliable. So I get the preference for TanStack. If it means anything, I do intend to continue maintaining the library with with proper functionality and TS types since I use it with my work. I've seen the concept work with a large, well-known company in the US (with an older implementation that was much more coarse); so I'm hopeful for the library's future.

Maybe I'm naive, but after leaning into the browser a bit more, I'm actually not sure if forms are too too hard. At least in part, I think that after the birth of React, people got accustomed to using stateful forms very quickly. Currently, many JS frameworks have various kinds of solutions for stateful forms. But it's actually the stateful forms that I think have made forms so difficult. (I could be wrong.) Which leads me to...

While I generally agree with the idea of leveraging the browser as much as possible, I wouldn't agree in this specific case.

Are there other significant examples of where you've found the browser to fall short when it comes to forms? You can easily access all of a form's data with FormData(form) or Object.frontEntries(FormData(form)). And since every form control has access to its owning form (e.g., input.form), you can pretty much access the entire application's form data as long as you have access to a single control. (You can also access all of a form's controls with form.elements) That means you get form data management without the need for state/providers/contexts/stores.

Similarly, a visited field is pretty much just a blurrred field. A simple event listener can track that. An event listener can also track dirty fields since the browser keeps track of default values. (Rough example)

I have indeed seen the insufficiencies of the email validation. 😅 That's why I think the ideal is to lean into the browser where possible, but enhance its capabilities where it falls short. From what I've seen (I'm not omniscient), the primary places where the browser falls short with forms are: A) Useful validation and B) Accessible error messaging. Most other things have seemed sufficient for me.

Regarding the former, the browser still has very useful validators. min*, max*, step, and required are all simple and helpful. The pattern attribute also seems to work well as long as the regex is properly formed. For anything that's insufficient (like email validation), a library only needs to expose a way to run custom validation logic (which the FormValidityObserver does).

Regarding the latter, a simple utility function is typically all that's needed for an entire web form to get accessible error messaging. (The FormValidityObserver handles this too.)


These are all just thoughts/considerations that I'm adding to the discussion. I'm not expecting you to be suddenly swayed from the TanStack, especially given that you've already invested some time into it.

@Christian24
Copy link
Author

Understandable! As the creator of the library, what's difficult about that is that a library doesn't have significant adoption until it has a significant adoption (even if it's good). But if I was on the consuming side, I would certainly want to know that what I was using was reliable. So I get the preference for TanStack. If it means anything, I do intend to continue maintaining the library with with proper functionality and TS types since I use it with my work. I've seen the concept work with a large, well-known company in the US (with an older implementation that was much more coarse); so I'm hopeful for the library's future.

Are you on Discord? I might be able to share with you in private what kind of forms we have been building.

Are there other significant examples of where you've found the browser to fall short when it comes to forms? You can easily access all of a form's data with FormData(form) or Object.frontEntries(FormData(form)). And since every form control has access to its owning form (e.g., input.form), you can pretty much access the entire application's form data as long as you have access to a single control. (You can also access all of a form's controls with form.elements) That means you get form data management without the need for state/providers/contexts/stores.

If I read the docs for FormApi correctly it does only support blob and string as values. How do you deal with controls that use numbers, bigint, objects etc. as value? Let alone arrays?

Similarly, a visited field is pretty much just a blurrred field. A simple event listener can track that. An event listener can also track dirty fields since the browser keeps track of default values. (Rough example)

I like the idea of using data attributes, however, these are string only also, right? I do not see how this is better than using a WeakMap for example.

Additionally, I think your idea is implying that form controls that are no longer part of the DOM, are not relevant anymore either. I'd argue it depends on your use case if you want that or not. I would hope a form library would give me both options.

I have indeed seen the insufficiencies of the email validation. 😅 That's why I think the ideal is to lean into the browser where possible, but enhance its capabilities where it falls short. From what I've seen (I'm not omniscient), the primary places where the browser falls short with forms are: A) Useful validation and B) Accessible error messaging. Most other things have seemed sufficient for me.

I agree the validation API is important, however, I am not sure how you support an array of controls with it?

Regarding the former, the browser still has very useful validators. min*, max*, step, and required are all simple and helpful. The pattern attribute also seems to work well as long as the regex is properly formed. For anything that's insufficient (like email validation), a library only needs to expose a way to run custom validation logic (which the FormValidityObserver does).

We actually had issues with pattern, since we needed a pattern that checks for none printable characters.

Regarding the latter, a simple utility function is typically all that's needed for an entire web form to get accessible error messaging. (The FormValidityObserver handles this too.)

Agreed, using builtin validations is best for accessibility.

These are all just thoughts/considerations that I'm adding to the discussion. I'm not expecting you to be suddenly swayed from the TanStack, especially given that you've already invested some time into it.

I am open to whatever people bring to the table. So no worries. This is also very brief what I came up with from the top of my head at a hotel room desk. ;) Also I have been mostly on the application building end, I am sure @crutchcorn has much more experience than myself.

@augustjk
Copy link

augustjk commented Dec 5, 2023

What we're finding is that how to do forms is quite an opinionated space and users' needs vary widely. I think providing multiple options for users is great and would support having multiple form package integrations with Lit.

With regards to the current state of this @lit-labs/forms RFC, the Lit team does not have enough opinions or bandwidth for a fully bespoke forms package, but we are happy to facilitate integrations with existing libraries. Particularly with interest from the those library maintainers, these can live in the particular form library's repo/ecosystem and do not have to be part of the @lit or @lit-labs org. To that end, @ITenthusiasm feel free to open a separate discussion if you have questions about integrating Form Validity Observer with Lit.

@ITenthusiasm
Copy link

ITenthusiasm commented Dec 5, 2023

@Christian24

Are you on Discord? I might be able to share with you in private what kind of forms we have been building.

Indeed I do! I'll ping you. 😄

If I read the docs for FormApi correctly it does only support blob and string as values.

My understanding is that it's basically strings and blobs as well, yes.

We actually had issues with pattern, since we needed a pattern that checks for none printable characters.

I guess that isn't surprising. So the browser isn't great at complex pattern matching, huh? 😔 At least it has the simple things down.

How do you deal with controls that use numbers, bigint, objects etc. as value? Let alone arrays?

Additionally, I think your idea is implying that form controls that are no longer part of the DOM, are not relevant anymore either. I'd argue it depends on your use case if you want that or not.

I agree the validation API is important, however, I am not sure how you support an array of controls with it?

This discussion is helpful! I think what's coming out is what I was looking for when I originally started building the FormValidityObserver. I wanted something that was powerful enough to give me everything that I needed for form validation, form data management, a11y, and progressive enhancement while also leaving a tiny footprint, functioning with pure JS, and allowing end users to write as little code as possible ... all while keeping a consistent dev-facing API (like the Testing Library Family). I also wanted to avoid state (because of React's bothersome behavior with re-rendering), and avoid the complexity of providers, higher order functions, etc.

I believe everything that's needed for forms is possible with that approach. But it does create some constraints that not everyone may prefer. Some examples:

  • Numeric values have to be represented as strings and converted to numbers where needed. (Not too inconvenient.)
  • Array/object data has to be simulated with regular form controls so that the form still works even if JS is disabled or unavailable in the browser. (This gives priority to progressive enhancement). This is possible, but it may not be convenient.
  • The DOM becomes the "state management tool". Form data comes from the form element, as does everything else. (The reason for data-* attributes is state management. For instance, const visitedFields = Array.from(form.elements).filter((f) => f.hasAttribute("data-visited").) To me, this is a pro because the experience/code doesn't change between frameworks.
  • Fields that should be hidden from the user and that should still be accessible via JS must have their visibility toggled instead of being removed from the DOM altogether.

What I like most about this approach is that I don't have to write anything complex in my markup, and I have something that works with or without a JS framework. So the HTML/JSX ends up much simpler, as does the API. But the tradeoff is that more thoughtfulness is required when it comes to how information is kept and how form data is sent to the server. (I give examples for these things in my docs so people aren't left in the dark.) And not everyone may want to be subjected to those tradeoffs.

I am open to whatever people bring to the table. So no worries. This is also very brief what I came up with from the top of my head at a hotel room desk. ;) Also I have been mostly on the application building end, I am sure @crutchcorn has much more experience than myself.

🙇🏾‍♂️

@ITenthusiasm
Copy link

@augustjk I'm discovering that as well. The discussion here has been very helpful/insightful for me. It's amazing how many ways forms can be done! When I start working on the Lit integration, I'll open another discussion for questions/feedback. Looking forward to seeing all of the solutions that Lit gets for forms.

@ITenthusiasm
Copy link

@augustjk Opened Discussion lit/lit#4437

@ITenthusiasm
Copy link

ITenthusiasm commented Dec 22, 2023

Well, for those who happen to straggle in and are interested, I did create an integration for Lit. Since Lit is a lot closer to pure JS than other frameworks with its Web Components approach, the API feels a lot like the core API -- just with slightly more convenience. Package: @form-observer/lit.

The goal of the FormValidityObserver is to give developers everything needed for form validation without re-inventing the browser's existing features. But I do have docs exemplifying how to leverage the browser's form features and how to JSON-ify FormData -- amidst other things. Some additional docs:

I hope this is useful. And I'm open to feedback in the GitHub Issues. Hopefully, as more options spawn, the form pain points in Lit will fade away.

@Christian24
Copy link
Author

As promised I took a stab at a Lit adapter for Tanstack form: TanStack/form#577 If anyone has feedback, please let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.