UdonSharp Attributes: SerializeField, Header, Tooltip, and Range

Created: 2025-12-19Last updated: 2026-09-09

Organize UdonSharp Inspector fields with SerializeField, Header, Tooltip, and Range, and learn why Range doesn't limit values set from code.

As a script grows, the Inspector's settings fields pile up. Seven number fields in a row, and it stops being clear which is brightness and which is a delay.

Open it again after a while and even you get lost. Hand the Prefab to someone else and it's worse.

This article covers using attributes to make the Inspector readable. The code's behavior doesn't change. Only how it looks to the person configuring it does.

A settings panel organized with dividers and headings

What You'll Learn

  • What Header, Tooltip, and Range each do
  • That Range only limits input and doesn't affect code
  • The difference between exposing a setting and syncing a variable
  • A guideline for not overusing attributes

Start from having tried variables and references.

Sponsored


Attributes are sticky notes on your code

An attribute is a [ ... ] line placed above a variable or method. It doesn't change the logic; it attaches an instruction saying "please treat this like so."

Picture sticking a note on your code. What the note says changes how Unity presents it.

Settings fields without attributes, compared with fields that have headings and sliders

Four are in common use.

AttributeWhat it does
[SerializeField]Puts a private variable in the Inspector
[Header("Heading")]Inserts a heading at that spot, creating a group
[Tooltip("Description")]Shows a description on mouse-over
[Range(min, max)]Turns the input field into a slider

None of them change the code's behavior. Only how it looks to the person configuring it. The effect is still large — configuration mistakes drop noticeably.

Sponsored

Hands-On: Organize a light's settings fields

We'll add attributes to a script that operates a light. When you're done, the Inspector splits into three groups and brightness is entered with a slider.

Once organized, the groups are visible and input happens on sliders

1. Look at it without attributes

Start from code with minimal attributes. Create LampSettings in Assets/Scripts.

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LampSettings : UdonSharpBehaviour
{
    [SerializeField] private Light targetLight;
    [SerializeField] private float dimIntensity = 0.2f;
    [SerializeField] private float brightIntensity = 2f;
    [SerializeField] private Color warmColor = new Color(1f, 0.85f, 0.6f);
    [SerializeField] private Color coolColor = Color.white;
    [SerializeField] private bool startBright;

    private bool isBright;

    private void Start()
    {
        isBright = startBright;
        Apply();
    }

    public override void Interact()
    {
        isBright = !isBright;
        Apply();
    }

    private void Apply()
    {
        if (targetLight == null) return;
        targetLight.intensity = isBright ? brightIntensity : dimIntensity;
        targetLight.color = isBright ? coolColor : warmColor;
    }
}

Attach it to a Cube and look at the Inspector. Six fields sit in a row with no dividers. You have to read the names to tell brightness from color.

2. Create groups with Header

Header does the most work. Placed above a variable, it inserts a heading at that spot.

[Header("Light to operate")]
[SerializeField] private Light targetLight;

[Header("Brightness")]
[SerializeField] private float dimIntensity = 0.2f;
[SerializeField] private float brightIntensity = 2f;

[Header("Colors")]
[SerializeField] private Color warmColor = new Color(1f, 0.85f, 0.6f);
[SerializeField] private Color coolColor = Color.white;

[Header("State at startup")]
[SerializeField] private bool startBright;

The Inspector splits into four groups. Settings of the same kind being adjacent is now visible at a glance.

Keep headings to three or four. A heading per field makes things harder to read, not easier.

3. Attach descriptions with Tooltip

Add descriptions to fields whose names don't convey the meaning.

[Header("Brightness")]
[Tooltip("Brightness in the off state. 0 turns it fully off")]
[SerializeField] private float dimIntensity = 0.2f;

[Tooltip("Brightness in the on state. Tune it to the room's size")]
[SerializeField] private float brightIntensity = 2f;

Hovering over that field in the Inspector shows the description.

You don't need one on every field. Limit them to things that are easy to get wrong, or whose units are unclear. Writing "brightness" as the description for "brightness" achieves nothing.

4. Turn it into a slider with Range

When a number has an upper and lower bound, a slider makes input faster.

[Header("Brightness")]
[Tooltip("Brightness in the off state. 0 turns it fully off")]
[Range(0f, 1f)]
[SerializeField] private float dimIntensity = 0.2f;

[Tooltip("Brightness in the on state. Tune it to the room's size")]
[Range(0f, 8f)]
[SerializeField] private float brightIntensity = 2f;

The Inspector's input fields become sliders. You can drag while watching the actual brightness to decide.

Moving the slider during Play changes the room's brightness in real time. Deciding a value gets much easier (changes during Play aren't saved, so re-enter the value you settled on after stopping).

5. Check your work

Look at the Inspector and confirm all four changed.

What to look atExpected display
Field namesdimIntensity is auto-formatted to Dim Intensity
GroupsDivided by the "Brightness," "Colors," and "State at startup" headings
Mouse-overA description tooltip appears
Brightness fieldsSliders instead of input boxes

In all four cases, not one line of the code's behavior changed. What changed is the effort required of whoever configures it later.

Range limits input, not runtime

This is the easy misunderstanding.

Range only sets the slider's span; assignment from code isn't limited

Even with [Range(0f, 8f)], code can put out-of-range values in.

brightIntensity = 100f;  // This goes through, Range or not

Range applies only to the Inspector's input field. It's a safety device for hand-entry, not a restriction on how the program behaves.

To keep a computed value inside a range for certain, limit it in code.

// Keep it within 0 to 8
float safeIntensity = Mathf.Clamp(brightIntensity, 0f, 8f);
targetLight.intensity = safeIntensity;

Mathf.Clamp is a function that trims out-of-range values to the nearest bound. A slider helps human input; Clamp protects the runtime value. Different roles, so use both.

Exposing in the Inspector and syncing are different

Here's another pairing that gets confused.

The Inspector is what the builder touches; syncing is what lines up among players
AttributeEffect
[SerializeField]Puts it in the Inspector. Tells other players nothing
[UdonSynced]Shares it with everyone. Doesn't appear in the Inspector

[SerializeField] is "a value the builder configures"; [UdonSynced] is "a value shared among everyone at runtime." Both are attributes on variables, and their purposes are completely different.

World settings (a brightness cap, say) are [SerializeField]; state that changes at runtime (whether it's currently on) is [UdonSynced]. Synchronization is covered in detail in networking basics.

public also puts things in the Inspector, but it additionally declares "other scripts may touch this." When you're just configuring it yourself, use [SerializeField] private.

Sponsored

Bonus: Good to Know Up Front

  • [Space] inserts a gap: For when a heading is too much but a little separation helps
  • [TextArea] suits long strings: It gives a wide field for multi-line text like instructional copy
  • [HideInInspector] does the opposite: For a public variable you don't want shown. Other scripts can still touch it
  • Attributes stack: You can put [Header], [Tooltip], and [Range] on the same variable, in any order
  • Where code stops being readable: Three attribute lines make one variable four lines long. Rather than adding them to every variable, limit them to the settings that matter

Summary

Attributes improve how things look to whoever configures them, without changing behavior.

  • Header creates groups. Three or four maximum
  • Tooltip goes only on fields that are easy to get wrong
  • Range only turns the field into a slider. Code can still go out of range
  • To protect the value at runtime, use Mathf.Clamp
  • [SerializeField] is for configuration, [UdonSynced] is for sharing. Different things

The question to ask about adding one is: "Will I remember what this field means a month from now?" If not, add a heading or a description.

Next, split logic out and reuse it. In methods and custom events, let's operate the same light from two buttons.

Read nextNext in the learning path
Syntax

UdonSharp Events and Execution Order: Start, Update, and Interact

Read this note
VRChat Notes in this section63