Message

A brief line of feedback at the top of the screen that goes away on its own, for the result of something the reader just did.

Usage

When to reach for Message, what each of its axes is for, and the decisions it leaves to you.

When to use

A message is one line of feedback at the top of the viewport that closes on its own. It reports the outcome of something the reader just did, and it has no title, no body, no actions and no close button.
  • The action worked and nothing else on the screen says so: “Copied”, “Saved”, “3 rows deleted”.
  • It did not work, the reason fits in a line, and trying again is the whole recovery: “Could not reach the server”.
  • Something the reader started is still running, paired with duration: 0 and a stable key so the result can replace it in place.
What to use instead
  • Notification when it needs a title, a body or its own actions. A message has room for one line and nowhere to put a Retry button.
  • Alert when the message belongs to the page rather than to the moment. A form that is invalid is still invalid three seconds later, and the message is gone.
  • Modal when it has to be answered. A message holds nothing focusable, takes no keyboard input and closes on a timer whether or not anyone read it.

The five types

type sets the glyph and the color, and nothing else. There is no size, no variant and no per-call placement.
  • success for the thing the reader asked for, done.
  • error for the thing the reader asked for, refused, when the reason fits in a line. If it needs more, use a Notification or an Alert.
  • info for something that happened without being asked for and is worth knowing.
  • warning for a success with a caveat, not for a softened failure.
  • loading for work in progress. It should rarely keep the default duration.
useMessage() gives each type your icon pack's glyph.
  • An icon node you pass wins for that one call.
  • icon: null does not remove the icon. A typed message falls back to its default glyph when icon is empty. Hide it with classNames: { icon: "hidden" }, or call open() without a type.
  • An untyped open({ content }) shows no glyph.

Duration, and what should not close itself

  • duration is in seconds. The default is 3, about six words of reading. Anything longer to read belongs in another component.
  • duration: 0 keeps the message up until you close it with destroy(key) or the function the call returns.
  • Give duration: 0 to anything the reader has to act on, any failure they will want to read again, and every loading that waits on a real request. Close those yourself.
  • pauseOnHover defaults to true. It does not help a reader who is not using a mouse, so do not count on it to rescue a message that closes too soon.

key, and updating one in place

  • Replace a message by its key. Without a key, every call opens a new message. With the same key, the second call updates the first: the message keeps its place while its glyph and text change.
  • Key by what the message is about (the record id, the job name), not by a counter. Then a double click cannot produce two messages.
  • destroy(key) closes that one message. destroy() with no argument closes all of them, which is useful on a route change.

The loading to result pattern

One message for the whole operation: open a loading with duration: 0 and a stable key, then call success or error with the same key when the request settles. The spinner becomes a tick without a second message.
  • Close it on the failure path too, in a finally. A loading message with no timer outlives the request that opened it.
  • Every call returns a close function you can await. const close = message.loading(...) closes it when called, and await message.success(...) resolves once the message has closed. Chain a second message off that promise to show it after the first instead of beside it.
  • Do not await the loading call itself. With duration: 0 it never resolves on its own.
  • If the request is fast enough that the spinner flashes, skip it. A loading message under about half a second reads as a glitch.

Where the instance comes from

There are three ways to reach a message API, and they are not interchangeable.
  • The static message.success(...) imported from antd renders outside your React tree, so your theme, direction and locale never reach it. It shows up in stock Ant Design colors.
  • App.useApp().message is the instance built by the <App> that AntuikitProvider renders, so it follows your theme.
  • useMessage() returns that same instance with your icon pack's glyphs. It returns the instance itself, not an [api, contextHolder] pair, so there is no holder to render. It throws outside AntuikitProvider.
Keep the static for code outside React, like a request interceptor, and accept that it is not themed. Inside a component, use the hook. Two instances on one page also mean two stacks and two sets of keys, so destroy() on one leaves the other's messages open.

How many is too many

One. A second message already makes the reader choose which to read first, and messages stack in the order they arrived, not by importance.
  • top, maxCount and stack are app-wide. They are not per-call fields, and AntuikitProvider does not set them, so nothing caps the number of messages and they list downward from 8px below the top.
  • Never fire one per item in a loop. Report the outcome in one line: “3 of 4 saved”, not four messages.
  • Never fire one during render or from an effect without a dependency guard. It fires again on every re-render.
  • If three are running at once, the screen is narrating work the reader did not ask about. Use a status area, an activity log or a Notification instead.