Photo & GIF Frame
Render an uploaded photo or animated GIF beside a label. The embedded fallback is a real two-frame GIF, so this example works before a user selects media.

Complete app
app.star
load("encoding/base64.star", "base64")
load("render.star", "render")
load("schema.star", "schema")
DEMO_GIF = """R0lGODlhIAAYAPQAAAAAAOxImexJmexJmuxLmuxMm+1OnO1Qnu1Tn+1Woe9mqvB0
svF9t/KBufOOwfSax/WdyfWjzfat0vi92/i/3PjC3vnG4PnH4fnN5PrR5/rS5/vZ
6/zn8wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBFAAAACwAAAAAIAAY
AAAFjSAgjmRpnmiqrkHrvnAsk3Jtv/Stx/m7VJzKIvYLDmG9VoTDZEZeyybniRv5
pM3jAss8tpJAbqUVxo5dSa5aXRW9LOs4x9IGXOVqbyAZiOKbVGhWMAsYfxt6X4M
xDXIaNXwvDnEJkIsyEGoKNpEwElgMN50wFE0QOqMwGhwUO6kvCBwIrpc7tCu4ub
q6IQAh+QQBFAAAACwAAAAAIAAYAIQAAACLXPaMXfaNXvaOYPaPYfaQY/aSZveUa
PeXbfeed/isi/mwkfm2mvq+pfq/p/rJtvvTxPzUxvzazvzbzvzbz/3g1/3t6f4A
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFkyAgjmRpnmiqrkHrvnAsk3Jtv/Stx7lL
MJPLhEF4/YLDIm70Slye0Eui5Yw+p64ewRolbLlPZaDHAD8ZZTMjy2wFzZM3eMI
Wucx4ax3gkufney5pf2BrLVqEZmI9AVWJUFiHbT5AhEkwjDANeJGYkzIPYAo2mT
EQVgs3pTESUA86qzAEFhcSO7EwBxcHtyu+v8DBIQA7"""
def main(config):
encoded = config.get("photo") or DEMO_GIF
image = render.Image(src = base64.decode(encoded))
return render.Root(
child = render.Padding(
pad = (2, 4, 0, 4),
child = render.Row(
expanded = True,
main_align = "space_between",
cross_align = "center",
children = [
render.Text(
"MEMORY",
font = "CG-pixel-3x5-mono",
color = "#ffffff",
),
image,
],
),
),
)
def get_schema():
return schema.Schema(
version = "1",
fields = [
schema.PhotoSelect(
id = "photo",
name = "Photo or GIF",
desc = "Image displayed inside the frame.",
icon = "image",
),
],
)
How it works
PhotoSelectstores the chosen image as Base64 configuration data.- The fallback uses the same shape, so
base64.decode()always givesImagereal image bytes. Imagepreserves the embedded GIF's frames and timing.- An expanded
Rowkeeps the label and image separated, while center alignment places the label halfway down the 24-pixel image area.
Keep production GIFs at 64×32, no larger than 128 KB, and verify the complete loop as described in the GIF guide.