How to pass props to partials

A partial's Props struct defines the typed interface the partial exposes to its caller. Struct tags control required, default, coercion, and query-binding behaviour. See the PK file format reference for the broader context.

Declare props

Inside the partial's <script>, declare a Props struct and accept it in Render:

type Props struct {
    Title       string `prop:"title"`
    Description string `prop:"description"`
    Count       int    `prop:"count"`
    IsActive    bool   `prop:"is_active"`
}

func Render(r *piko.RequestData, props Props) (Response, piko.Metadata, error) {
    return Response{
        Title:       props.Title,
        Description: props.Description,
        Count:       props.Count,
        IsActive:    props.IsActive,
    }, piko.Metadata{}, nil
}

The prop:"name" tag maps a Go field to an HTML attribute name. If omitted, the HTML attribute defaults to the lowercase field name.

Pass values from the caller

The leading colon : controls whether the attribute value is an expression or a literal string. Use the bare prop name (matching the prop:"..." tag) for the attribute key:

<piko:partial is="card"
  :title="state.Product.Name"
  :description="state.Product.Description"
  :count="state.CartCount"
  :is_active="state.IsActive" />

The same call also works with the server. prefix - the compiler treats server.title and title as the same prop. Use the prefix when you want to be explicit that an attribute is a prop and not a passthrough HTML attribute:

<piko:partial is="card" :server.title="state.Product.Name" />

A bare attribute that does not match any prop name forwards as a plain HTML attribute on the partial's root element. The server. prefix instead raises a build-time "Unknown prop" diagnostic, which is useful when you want the compiler to catch typos. The request. prefix has the same prop semantics but additionally pulls request-scoped values into the partial.

Coerce strings to typed values

Add coerce:"true" to accept raw string literals and convert them to the field's type:

type Props struct {
    IsActive bool    `prop:"is-active" coerce:"true"`
    Count    int     `prop:"count" coerce:"true"`
    Price    float64 `prop:"price" coerce:"true"`
}

With coerce:"true" the caller can pass a string literal (no leading colon) and Piko parses it into the field's type at compile time. Bare or server.-prefixed attributes both work:

<piko:partial is="display" is-active="true" count="42" price="19.99" />

Require a prop

Mark a prop as required with validate:"required". A missing attribute fails the build:

type Props struct {
    Title string `prop:"title" validate:"required"`
    Theme string `prop:"theme"`
}

Build error:

error: Missing required prop 'title' for component <card>

Set a default value

Static defaults use default:"value":

type Props struct {
    Theme string `prop:"theme" default:"light"`
    Size  string `prop:"size" default:"medium"`
}

For defaults that do not fit a string literal, use a factory function. The function must take no arguments and return the prop type:

func GetDefaultOptions() AvatarOptions {
    return AvatarOptions{Size: 48, Shape: "circle"}
}

type Props struct {
    Options AvatarOptions `prop:"options" factory:"GetDefaultOptions"`
}

Piko calls the factory at render time when the caller omits the prop.

default and factory are mutually exclusive on a single field.

Bind a prop to a query parameter

query:"param" binds a prop to a URL query parameter when the caller does not supply a value:

type Props struct {
    Page int `prop:"page" query:"page" coerce:"true"`
}

A request to /products?page=2 populates Page = 2 when the caller omits an explicit page attribute.

Query binding requires string, *string, or a type with coerce:"true". Slices and maps are not supported.

Optional props with pointer types

Pointer types mark a prop as truly optional. The pointer is nil when omitted:

type Props struct {
    Title    string              `prop:"title"`
    Subtitle *string             `prop:"subtitle"`
    Profile  *models.UserProfile `prop:"profile"`
}

Callers pass pointer-type props the same way. Piko takes the address automatically when the caller supplies a non-pointer expression.

Combine tags

Multiple tags coexist on a single field:

type Props struct {
    Title string `prop:"card-title" validate:"required"`
    Theme string `prop:"card-theme" default:"default"`
    Priority int `prop:"priority" coerce:"true" default:"1"`
    Options models.Config `prop:"options" factory:"GetDefaultConfig"`
    Page int `prop:"page" query:"page" coerce:"true"`
}

Dynamic defaults inside Render

For values that depend on the request, set defaults inside Render instead of in struct tags:

func Render(r *piko.RequestData, props Props) (Response, piko.Metadata, error) {
    if props.Locale == "" {
        props.Locale = r.Locale()
    }
    return Response{Locale: props.Locale}, piko.Metadata{}, nil
}

r.Locale() returns the resolved request locale. Pair it with r.DefaultLocale() for the configured fallback. There is no r.AcceptLanguage() accessor on RequestData.

See also