A wider screen, please

This page features interactive demos and side-by-side code samples that need at least 340 pixels of horizontal space. Please widen your browser window, or rotate your device, to continue.

Morpheus v0.1.0
v0.1.0
Morpheus Server-driven architecture Settings

Server-driven architecture#

Even though Morpheus is entirely framework and architecture agnostic, its original purpose was to enable easier development of hypermedia web app architectures, where the server owns the truth and the browser gets HTML (morph-)patches and renders them with no additional layers of abstraction; as opposed to JSON -> [vDOM ->] DOM.

Morpheus components make it easy to drive your application with just a single HTML template for each page using "fat morph" patches (see The Tao of Datastar ).
Reactive signals also allow you to easily interconnect components on a page.

Every Morpheus demo uses Datastar

In order to avoid hosting a server and to be able to deliver this site via CDN, we are using An in-browser simulator script which hijacks Datastar's fetch requests, executes the JavaScript handler and then fires a Datastar-native datastar-patch-elements event. which is similar to a regular Datastar request handler.

Data flow#

  1. A component dispatches an event (like neo-dialog-open, neo-popover-open, etc.).
  2. Datastar reacts via the data-on:<event>="@post('/path')" event handlers and dispatches an action (an HTTP request).
  3. The server then re-renders the entire page template and asynchronously streams the new HTML via a Brotli-compressed SSE stream (Server-Sent Events). Datastar then morph-patches that HTML into the live DOM.
  4. Morpheus web components observe changes in the DOM and update their internal state and presentation.
Action -> PatchServerBrowserCallAction(HTTP request)DOM (HTML via SSE)Patch

Typically, a production-grade Datastar application implements a CQRS (Command Query Responsibility Segregation) architecture. This is an example schema of how approximately this would look when implemented in the Go programming language:

Browser Tab 1Browser Tab 2CQRSActionsSSESSEMessageBroker(e.g. NATS)ServerGoroutine 1Goroutine 2Command Query Responsibility SegregationTruthStateSignals(JSON)State

Command patches#

Many Morpheus components are stateful, and server-side morph patching must only interfere in the user's client-side interactions when the server explicitly wants it to happen. For example, a fat morph of the page must not close a neo-popover after the user opened it, unless the server explicitly needs it to happen.

Therefore every Morpheus attribute follows one rule: an absent attribute is not a command. A brand-new element falls back to the attribute's default; an element already in the DOM keeps its current value. Every boolean attribute also accepts an explicit ="true" or ="false", so the server can command either state or stay silent and leave the client's alone.

Two kinds of attribute, one rule:

  • Interactive state the user drives (open, value, checked, pressed, expanded, page): an absent attribute keeps the current client state, so a fat morph that omits it can't reset what the user just did.
  • Configuration the server owns (flip, hover, disabled, clamp-placement): an absent attribute falls back to the default. The client never changes these, so the server stays the source of truth.

The server can still explicitly dictate Datastar to...

  • close a popover by setting open="false", or to explicitly open it using open="true".
  • explicitly set a certain value on a neo-textinput by setting attribute value="specific value".

Every boolean attribute reads the same three ways. Taking open, which applies to <neo-popover>, <neo-menu>, <neo-drawer>, <neo-sidebar>, <neo-contextmenu>, and <neo-textinput>'s suggestions:

  • open absent is no command. The component keeps its current state, so a morph that omits open can't disturb it.
  • open="false" commands the component to close (case-insensitive).
  • open or open="true" commands the component to open.

Switch between the states manually or enable auto-play to see the command patches in effect:

No command
Open
Close
Deactivate Rename Duplicate
Delete
New state Toggle autoplay
Account

Account

Signed in as Alice Larsson.

HTML CSS

The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.

Replace Patch

Signals

Datastar signals declared in the selected state's markup. Change a value to drive the preview, applied live while Sync is on, or when you click Patch.

Patch

Essentially, an absent attribute means "no command", the client may keep its current state until the server genuinely needs to take control.

Loading indication#

For components like combobox for the contents to be fetched lazily, the initial HTML must contain the placeholder slot [data-neo-async-placeholder], which the combobox displays when it has no options defined yet. The combobox then posts on open; the server streams the options into #<id>-options and the kit swaps the placeholder out. The delay keeps the placeholder visible. Open the combobox to see it.

Loading…

Error handling#

If the server is temporarily unavailable due to network issues or errors, the UI must provide the user with feedback. Sometimes, that feedback can simply be provided via toasts. Morpheus offers useful template wrappers like for example datastar.ComboboxAsync:

In the following example, the server always returns a 500 Internal Server Error immediately. Datastar retries on error twice, waiting 1s between attempts; once the retry budget is exhausted, the combobox swaps in a failure state with a retry button. Async load with failure swap shows the same pattern with live retry controls.

Loading…

Settings

Theme

Default Sunshine Ocean Matrix Rose

Accessibility

Playgrounds

Server Simulator