sRFC 29 - Input types of blinks and actions

sRFC 29 - Input types of blinks and actions

TLDR

Add new input types within blink clients to improve the user experience and unlock new use cases for Action builders.

Rationale

The current Solana Actions and blinks specification only supports a single, generic input type of a non-structured text box. While this basic input is useful, having more explicit and declarative input types would allow Action builders and blink clients to unlock new use cases and improve the user experience of using blinks across the internet.

Current Spec

Per the current specification, an Action api will declare what user input fields it desires using the structured response (ActionGetResponse) from the initial GET request, specifically via any child item of links.actions containing the parameters field.

The current specification is this:

/**
 * Response body payload returned from the Action GET Request
 */
export interface ActionGetResponse {
  /** image url that represents the source of the action request */
  icon: string;
  /** describes the source of the action request */
  title: string;
  /** brief summary of the action to be performed */
  description: string;
  /** button text rendered to the user */
  label: string;
  /** UI state for the button being rendered to the user */
  disabled?: boolean;
  /**  */
  links?: {
    /** list of related Actions a user could perform */
    actions: LinkedAction[];
  };
  /** non-fatal error message to be displayed to the user */
  error?: ActionError;
}

/**
 * Related action on a single endpoint
 */
export interface LinkedAction {
  /** URL endpoint for an action */
  href: string;
  /** button text rendered to the user */
  label: string;
  /** parameters used to accept user input within an action */
  parameters?: ActionParameter[];
}

Any ActionParameter declared will result in a plain text input be rendered to the user (with the optional placeholder text) in order to accept their input, which ultimately gets sent the Action api endpoint via the POST request.

Additionally, the user input data is only sent to the Action api via query parameters and template literals (e.g. {name}) declared within the specific linked action’s href field. This presents limitations and complexities when accepting longer user input values or more complex input types.

/**
 * Parameter to accept user input within an action
 */
export interface ActionParameter {
  /** parameter name in url */
  name: string;
  /** placeholder text for the user input field */
  label?: string;
  /** declare if this field is required (defaults to `false`) */
  required?: boolean;
}

Proposal

  1. To support sending longer and more complex user input to the Action API (via the POST request), user input should be optionally sent via the POST request body, not just templatized query parameters (like the user’s account address is already being sent).
    All user input should be sent to the Action API as follows for each LinkedAction:

    • any input parameters specified via template literals in query parameters of the href value should have their respective user input values sent via the its named query parameter (this is how the spec currently works)
    • all remaining input parameters (aka those that do NOT have a template literal declared in the href ) should be sent in the body of the POST request (along side the user’s account, but should never be able to modify the account value)
    • Note: if a template literal for any of the parameters is found, it should not be sent via the body. This will reduce the total data sent over the network and is always a best practice to not send duplicate data.

    In the end, this allows developers to accept user input via query parameters (not breaking existing applications) or via the POST request body. And effectively set an implicit default of using the POST body to receive the user input, just like a typical HTML form.

  2. Update the ActionParameter to support declaring different types of user input. In many cases, this type will resemble the standard HTML input element.

    This new new type attribute should have a type declaration as follows:

/**
 * Input type to present to the user 
 * @default `text`
 */
export type ActionParameterType =
  | "text"
  | "email"
  | "url"
  | "number"
  | "date"
  | "datetime-local"
  | "checkbox"
  | "radio"
  | "textarea"
  | "select";

Each of the proposed type values should normally result in a user input field that resembles a standard HTML input element of the corresponding type (i.e. <input type="email" />) to provide better client side validation and user experience:

In addition to the elements resembling HTML input types above, the following user input elements are also supported:

  • textarea - equivalent of HTML textarea element. Allowing the user provide multi-line input.
  • select - equivalent of HTML select element, allowing the user to experience a “dropdown” style field. The Action api should return options as detailed below.

When type is set as select, checkbox, or radio then the Action api should include an array of options that each provide a label and value at a minimum. Each option may also have a selected value to inform the blink-client which of the options should be selected by default for the user (see checkbox and radio for differences).

 interface ActionParameterSelectable extends ActionParameter {
  options: Array<{
    /** displayed UI label of this selectable option */
    label: string;
    /** value of this selectable option */
    value: string;
    /** whether or not this option should be selected by default */
    selected?: boolean
  }>;
}

If no type is set or an unknown/unsupported value is set, blink clients should default to text and render a simple text input (just as they do now).

The Action API is still responsible to validate and sanitize all data from the user input parameters, enforcing any “required” user input as necessary.

For platforms other that HTML/web based ones (like native mobile), the equivalent native user input component should be used to achieve the equivalent experience and client side validation as the HTML/web input types described above.

Note: As with the current spec, if a LinkedAction does not declare the parameters attribute, then no user input is requested by the Action api and blink clients should continue to render a single button that performs the POST request to the href endpoint. This proposal does not change that.

Closing Notes

With the support of more input types, developers will be able to build more complex blinks and accept more user input. Having “too many” user input fields could result in a reduced user experience and also make users less likely to actually enter all the requested inputs.

To avoid UI bloat and degrading user experiences, blink-clients are likely to impose “soft limits” on the number of input fields they display. While this Actions/blink specification avoids taking an opinionated approach on the UI layer of blinks, the following is a reasonable guideline (and used by Dialect’s blinks SDK today):

  • 10 buttons + 3 inputs (if separate Actions)
  • 10 inputs (if it’s a form, and other actions are not rendered if there is a form in the response)

As such, Action API developers should limit the number of input parameters they request as blink-clients may limit how many input fields get shown to users. Developers should also keep in mind that if a user does not see all input fields (because the blink-client soft limits them as described above), then the user will have no way to enter a value. Therefore if all these fields are required by the Actions API, the user will NOT be able to actually execute your Action and get a transaction.

3 Likes

For the user input, instead of having different types for text , email , url, number , a general tag regexp can be used, which would provide more flexibility.

The ActionParamaterType can then be modified to

/**
 * Input type to present to the user 
 * @default `regexp`
 */

export type ActionParameterType =
  | "regexp"
  | "date"
  | "datetime-local"
  | "checkbox"
  | "radio"
  | "textarea"
  | "select";

The following regex pattern can be used for the text, email, url , number

  1. text - .*
  2. email - ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
  3. url - ^(https?|ftp):\/\/[^\s/$.?#].[^\s]*
  4. number - ^-?\d+(\.\d+)?

any other regex can be used for more specific input types.

If ActionParamterType is set to regexp then the input field will be like

<input type="text" id="emailInput" name="emailInput" pattern="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" required>

With the support of regexp, the input field provides a more flexible approach.

3 Likes

Having a regex option makes a lot of sense, but could be really annoying for people out of the box that just want the benefit of simple input types like email or url.

We could support both though! Adding an additional type=regexp and accepting an additional regexp value in the ActionParameter type. This way people get the best of both worlds

Edit:
@0xaryan we could also simplify even more by adding the regexp attribute to the ActionParameter so it could be applied to any user input type, no matter the type.

The benefit of using the natively support input types (like email, url, etc) is that browsers and native clients will give the better UX and help enforce that input type in a native way (like if email is set and you try to enter a non email, the browser will not let you submit the form). Vice if you only use a regex, then the blink client would be the only layer of client side validation there, allowing users to still submit

2 Likes

This is my official update to the proposal above, specifically point (2)
PS: For some reason I could not actually update the original post


  1. Update the ActionParameter to support declaring different types of user input and allow providing regular expression patterns for more complicated input.

The updated ActionParameter should be updated as follows:

/**
 * Parameter to accept user input within an action
 */
export interface ActionParameter {
  /** input field type */
  type?: ActionParameterType;
  /** regular expression pattern to validate user input client side */
  pattern?: string;
  /** human readable description of the `pattern` */
  patternDescription?: string;
  /** parameter name in url */
  name: string;
  /** placeholder text for the user input field */
  label?: string;
  /** declare if this field is required (defaults to `false`) */
  required?: boolean;
}

The pattern should be a string equivalent of a valid regular expression. This regular expression pattern should by used by blink-clients to validate user input before before making the POST request. If the pattern is not a valid regular expression, it should be ignored by clients.

The patternDescription is a human readable message that describes the regular expression pattern. If pattern is provided, the patternDescription is required to be provided.

If the user input value is not considered valid per the pattern, the user should receive a client side error message indicating the input field is not valid and displayed the patternDescription string.

(the remaining contents of section (2) remains the same)

2 Likes

Having the input types coupled with regexp makes more sense, and I strongly agree on making a better UX for the user.

The pattern and patternDescription will create a good UX and also offer more design space to the developer.

4 Likes

PR has been opened on the Actions spec repo:

4 Likes

Given current support of passing arguments is limited to url encoded query parameters (through string templates) and there is still a possibility to pass them as such, I think it’s worth specifying how are they passed for multiple option types (checkbox). I propose for types that are considered multi-option (checkbox, or maybe select with multiple in the future) to be inserted as a single comma-separated string.

Example:
href: "/?test={test}" , where test is a parameter type checkbox with options (just values for simplicity): 1, 2, 3, 4 . If user selects 1, 4 , then the request will be made with ?test=1,4 (url encoded)

POST body would return actions with multiple values as arrays of strings.

2 Likes

Also several other suggestions:

POST request parameters
Since the Actions spec may change in the future with proposals to add additional fields to POST request body, I think it’s worth moving parameter values to a separate object inside POST request body. This would avoid potential conflicts with incoming parameter action names.

POST request body type can look something like this:

{
  account: string;
  /** 
    Map of parameter values from user's interaction
    key - parameter name
    value - input value (by default `string`, if multi-option, `Array<string>`
  */
  data?: Record<string, string | Array<string>>;
}

Min/Max options
Most of the parameter types can support min and max natively in some way or another. Adding them would strongly help developers achieve better UX with forms or single actions.

Expected behavior:
text, url, email, textarea

  • min and max represent the minimum and maximum string length required. (should follow minlength and maxlength HTML attributes)

date, datetime-local

  • min and max represent the borders for date selection (should follow min and max HTML attributes)

number

  • min and max represent the minimum and maximum for the numerical value (see date, datetime-local links)

checkbox

  • min and max represent the amount of checked boxes that user can select

select, radio

  • not applicable

If select will support multiple options, then it would follow the same rules as checkbox.

Pattern & Pattern Description
pattern can be provided to all parameter types (except select, radio, checkbox, date, datetime-local).
If pattern present, type is not empty and not equal to text, then type would become a more stylistic property (for blink clients to render the input styled for the type) and use pattern to validate the entered value.

patternDescription is definitely important for cases when pattern is specified, but also can be valuable for other parameter types for better explanation to the user. Which is why I’d like to suggest renaming to just description or more specific settingsDescription or validationDescription.

Would love some feedback around these points.

4 Likes

This is a good callout @fsher, thanks for bringing it up. In my head I was assuming that a multi select option like checkbox would function the same as a traditional HTML form, but this could become inconsistent easily. Especially when considering non-web based platforms.

So I agree with making it more explicit in the spec, and your proposal looks good to me.

My one thought on it is that if the comma separated value is sent in a url query param: should we specify anything about the value being uri encoded, but not the commas?
Depending on the value that the Action API declares in the option, it could have spaces or special characters which would be required to uri encode/decode. And I think having the comma uri encoded might potentially having issues when parsing it server side?

In my head, ideally people would not use a query param for checkboxes and instead have it sent in the body, but you never know what a dev might do lol

2 Likes

POST request parameters
Love this. it helps keep things more clean. You have my vote!
Do you think it is worth it in the spec types to allow developers to specify the keys for the data fields? This could improve DX if they decide to use it.

Something like this:

/**
 * Response body payload sent via the Action POST Request
 */
export interface ActionPostRequest<T = string> {
  /** base58-encoded public key of an account that may sign the transaction */
  account: string;
  /** 
    Map of parameter values from user's interaction
    key - parameter name
    value - input value (by default `string`, if multi-option, `Array<string>`
  */
  data?: Record<keyof T, string | Array<string>>;
}

Min/Max options

Adding min/max options make sense as it can provide a better UX on the client side and is an easy add too. Using them with checkboxes is an interesting idea too, I don’t think I have ever seen that in the wild anywhere, but it could be useful for people.

Pattern & Pattern Description

Your clarification on pattern not actually applying to select, radio, checkbox, date, datetime-local is fair. I had the assumption of exactly this when writing it up, but we can make it more explicit in the spec details. Especially to help craft better types in the @solana/actions-spec package.

For the description, are you suggesting that this could simply be displayed to the user for any parameter field that’s declared regardless of if a regex pattern is being used?

  • If so, I’m game. I think it helps developers provide a better UX
  • If not, can you clarify?

My one thought on it is that if the comma separated value is sent in a url query param: should we specify anything about the value being uri encoded, but not the commas?

I believe since it’s in query params, the whole string should be encoded, including the commas. Servers should parse that into a regular string, or potentially just decode with decodeURIComponent (or similar in other languages). But I definitely agree, this is something that can be hard to debug. At the same time, since the spec now prefers sending values inside POST body, it could be ok (just a limitation that can be documented).

What do you think?

2 Likes

Do you think it is worth it in the spec types to allow developers to specify the keys for the data fields? This could improve DX if they decide to use it.

Yes, love it! Better typing, better DX!

Adding min/max options make sense as it can provide a better UX on the client side and is an easy add too. Using them with checkboxes is an interesting idea too, I don’t think I have ever seen that in the wild anywhere, but it could be useful for people.

Yep, it’s just an option that can be implemented. E.g. devs can be doing questioners or group together checkboxes to make them all required (like have 2 checkboxes and min to be 2). Again, just wild guesses, I’m sure people will think of something!

For the description, are you suggesting that this could simply be displayed to the user for any parameter field that’s declared regardless of if a regex pattern is being used?

The first one, yes. Sorry, should’ve made myself clearer :D. A description for the field to explain what’s expected in the input and potentially an explanation why the validation fails.

again, wild (and not the best) example:
type: "url", max: 20, description: "Please provide a shortened link (e.g. bit.ly)"

description can initially show as a label, but if user somehow entered more that 20 symbols (assuming it’s possible), that description could become an error indicator inside the client.

2 Likes

Understood! I like it all :slight_smile:

the whole string should be encoded, including the commas.

I think this might cause issue with complicated values, but I think it is fine just being a limitation because the POST body.
I’m okay with the simple blanket statement to the effect of “uri encode the comma separated list, but we recommend using the query param for it”

3 Likes

Just to be clear, POST body should receive an array of strings. If parameter used in query params, then it should become a comma-separated uri-encoded string.

2 Likes

Sure! It sounds like adding more input types to blinks will really enhance what developers can do with them. Keeping the user experience in mind by limiting the number of input fields makes a lot of sense too.

3 Likes

Yup, we are on the same page

4 Likes