Allan's workaround is the best you get, but let me try to explain. Even using plotly natively, we cannot get the effect you are after, since error bars are always drawn last. This is because error_y traces are created with WebGL engine and those traces are always drawn after the regular scatter traces, regardless of the order they were added into the plot.
You can see it here; even though we are adding the bars after adding the error-y, they are plotted first and hence the error-y's cap is visible.
library(plotly) df <- data.frame(y = c(5, 10), group = c("a", "b")) error_length <- 10 plot_ly() %>% add_trace( x = df$group, y = df$y + error_length/2, type = "scatter", mode = "markers", marker = list(color = "transparent"), error_y = list( type = "constant", value = error_length/2, color = "black", thickness = 0.4, width = 20), name = "Positive Errors", showlegend = FALSE) %>% add_trace(inherit = FALSE, type = "bar", x = df$group, y = df$y, color = I("lightgrey"))

The workaround in plotly is to draw shapes instead of a bar plot (shapes are also drawn with WebGL):
fig <- plot_ly() %>% add_trace( x = as.integer(as.factor(df$group)), y = df$y + error_length/2, type = "scatter", mode = "markers", marker = list(color = "transparent"), error_y = list(type = "constant", value = error_length/2, color = "black", thickness = 0.4, width = 20), name = "Positive Errors", showlegend = FALSE) %>% layout(xaxis = list(range = as.integer(as.factor(df$group)) + c(-0.5, 0.5), tickvals = seq_along(df), ticktext = df$group), yaxis = list(zeroline=FALSE)) bar <- list(type = "rect", fillcolor = "lightgray", line = list(color = "lightgray"), xref = "x", yref = "y") bars <- list() bar_width <- 0.3 for (i in seq_along(df)) { bar[["x0"]] <- i - bar_width bar[["x1"]] <- i + bar_width bar[["y0"]] <- 0 bar[["y1"]] <- df$y[[i]] bars <- c(bars, list(bar)) } layout(fig, shapes = bars)
