Framework DSL
Flor's interface code can be written as a set of normal Rust DSL: functions are responsible for composing views, parameters are responsible for passing in properties, signals and handlers, and the return value is an already configured view.
This has similar usage feeling to JSX: page is not a large string template, but a set of view functions that can be split, reused, passed parameters. Difference is Flor doesn't need extra template language, composition itself is Rust's function calls and chain methods.
If not yet familiar with how .class(...)、.layout(...)、.style(...) multiple calls merge, first see Resolver Layer Mechanism.
How to Express View Tree
Flor's most common child view list writing is views![...]:
views![...] accepts a set of view expressions, and converts them into view list. It's suitable for expanding child nodes at view tree site, especially when directly writing multiple child views in container.
If only need to convert single view into generic view object, can use view!(...):
Macro is not a required entry point. It just makes writing a view tree in place more convenient. When you need to pass a single view according to some parameter type, directly calling into_view(), or passing the value to an API that accepts IntoViewIter, is often clearer.
IntoView and IntoViewIter
IntoView / IntoViewIter are used to convert views into Flor's more generic view forms. They are not only used for method return values, but also for view parameters, module parameters, container children, and other places where types need to line up.
In the current public API, single-view conversion is IntoView, while child-view sequence conversion is IntoViewIter. A single view also implements IntoViewIter, so passing only one child no longer requires manually converting it into a list first. When encapsulating functions, a single view can return impl View or impl IntoView; multiple views can return impl IntoViewIter:
title_view() returns a normal view, caller can still continue chaining .class(...), .layout(...) etc. builders. title_child() more emphasizes "this return value will be taken as single child view passed in". action_buttons() then indicates this function returns a set of child views.
For example, containers such as div(...) accept impl IntoViewIter. When there is only one child view, you can pass that view directly:
If a view or module parameter wants one child view, mark it as impl IntoView; if it wants a set of child views or should accept either a single child or a list, mark it as impl IntoViewIter:
Key point of writing this way is to let parameters and return values express clearly: here needs normal view, generic view object, or a set of child views that can be passed on to a container.
Macro and conversion capabilities can be used together. views![...] is suitable for writing multiple child nodes in place, and into_view() is suitable for aligning an existing view expression to a generic view object:
Continue Chaining After Return
IntoView inherits ViewIdentity, and common builders now use ViewIdentity to read view identity. This means that when a function returns impl IntoView, the caller can still continue chaining .class(...), .layout(...), .on_click(...), .focus_index(...), and similar builder methods:
This kind of encapsulation no longer needs to force a return type of impl View just to keep chaining available. When the function means "return a value that can be passed as a child view", impl IntoView is more direct.
Why Need Macro
Flor provides views![...], is to make writing child view list on site more handy, meanwhile avoid Rust tuple's length problem in UI child node expression.
Floem and similar frameworks can use tuple to express child views, for example (a, b, c). This writing is very natural, but in stable Rust, framework usually needs to separately implement trait for different length tuples. That is, it's not infinite length: how many child nodes supported, depends on how many groups of tuple implementations framework wrote.
views![...] will directly expand into view list, doesn't need to separately implement trait for each child node quantity, also doesn't easily let user hit tuple length limit when child view quantity increases.
Later adding tuple support is feasible, practice usually is to supplement implementation for common length tuples, let user can write (a, b) or (a, b, c) in simple scenarios. But as long as stable Rust doesn't have true variadic generics, tuple support itself will still have a framework defined limit. So even if later supports tuple, views![...] is also more suitable as stable writing when no fixed child node quantity.
Method Composing Views
Minimum encapsulation is writing a view's layout, style and events into function, then returning it:
Here Args is the inference marker for the event handler's parameter form. With this signature, callers can pass a full click handler, a no-argument handler, a ViewId-only handler, or a handler without ViewId. If this used the old impl Into<OnClickHandler> form, only the full-signature From conversion would remain, and simplified parameter forms would be lost.
Caller only needs to care about this view's exposed parameters:
Here primary_button is like a small view. How it internally writes class, how binds events, for caller are all collected into function boundary.
Composing into Module
Multiple views can also be encapsulated into a composite view or business module. For example login module can encapsulate title, code button, login button and layout together:
Use a separate Args generic for each handler parameter, such as LoginArgs and SendCodeArgs here. They have no business meaning; they only let each handler infer its own parameter form independently.
This function returns not template fragment, but a complete view. Caller can pass handler as parameter:
If module needs reactive state, can also pass signal as parameter, let module internally only control properties it cares about. For example whether login button is available, code countdown, whether panel is displayed, can all be independently split into their own parameters, instead of writing entire layout and style as complex string concatenation.
Continue Configuring After Return
View returned by function can still continue using builder. That is, module internally can write default layout and style, caller can externally continue supplementing or overriding:
Here login_module internally already wrote flex flex-col gap-3 p-4, caller only supplemented width. Because Resolver Layer Mechanism will merge configuration by layer, so external supplement won't clear module internal configuration; if externally rewrote same property, then later written is standard.
Why Suitable for AI
Flor's this set of DSL walks common functional UI expression path: methods compose views, properties, signals and handlers pass as variables, return value continues participating in composition.
Large amount of AI models are already familiar with React, JSX, functional components and declarative UI organization patterns, so they more easily migrate to Flor's writing: recognize view tree, complete properties, split modules, adjust layout and event binding.
This is also reason mentioned in AI page: Flor didn't design this way for AI, but this DSL API naturally walked onto expression path AI already trained on, and is also more easily understood.

